From 4910263c93045cec1b7bbc7f4f5c368bf1313bf6 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:04 +0200 Subject: [PATCH 001/133] refactor(chat): add streaming/shared/ package for StreamResult and utils Foundation layer for the parallel refactor of stream_new_chat.py. Extracts the StreamResult dataclass (tracks per-turn streaming state) and a small set of shared utilities (resume_step_prefix, safe_float). Add-only; no existing code imports from this package yet. Existing stream_new_chat.py keeps its inline equivalents until cutover. --- .../tasks/chat/streaming/shared/__init__.py | 15 ++++++++ .../chat/streaming/shared/stream_result.py | 37 +++++++++++++++++++ .../app/tasks/chat/streaming/shared/utils.py | 27 ++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/shared/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/shared/utils.py diff --git a/surfsense_backend/app/tasks/chat/streaming/shared/__init__.py b/surfsense_backend/app/tasks/chat/streaming/shared/__init__.py new file mode 100644 index 000000000..6c9f1f6b5 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/shared/__init__.py @@ -0,0 +1,15 @@ +"""Shared building blocks used across every streaming flow.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.tasks.chat.streaming.shared.utils import ( + resume_step_prefix, + safe_float, +) + +__all__ = [ + "StreamResult", + "resume_step_prefix", + "safe_float", +] diff --git a/surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py b/surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py new file mode 100644 index 000000000..a940e8a9f --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py @@ -0,0 +1,37 @@ +"""Per-turn streaming state shared between the orchestrator and event loop.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class StreamResult: + accumulated_text: str = "" + is_interrupted: bool = False + sandbox_files: list[str] = field(default_factory=list) + request_id: str | None = None + turn_id: str = "" + filesystem_mode: str = "cloud" + client_platform: str = "web" + intent_detected: str = "chat_only" + intent_confidence: float = 0.0 + write_attempted: bool = False + write_succeeded: bool = False + verification_succeeded: bool = False + commit_gate_passed: bool = True + commit_gate_reason: str = "" + # Pre-allocated assistant ``new_chat_messages.id`` for this turn, captured by + # ``persist_assistant_shell`` right after the user row is persisted. ``None`` + # for the legacy/anonymous code paths that don't opt in to server-side + # ``ContentPart[]`` projection. + assistant_message_id: int | None = None + # In-memory mirror of the FE's assistant-ui ``ContentPartsState``, populated + # by the lifecycle methods called from the streaming event loop at each + # ``streaming_service.format_*`` yield site. Snapshot in the streaming + # ``finally`` to produce the rich JSONB persisted by + # ``finalize_assistant_turn``. ``repr=False`` keeps the log-on-error path + # (``StreamResult`` is logged in some error branches) from dumping a + # potentially-large parts list. + content_builder: Any | None = field(default=None, repr=False) diff --git a/surfsense_backend/app/tasks/chat/streaming/shared/utils.py b/surfsense_backend/app/tasks/chat/streaming/shared/utils.py new file mode 100644 index 000000000..fe6901543 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/shared/utils.py @@ -0,0 +1,27 @@ +"""Small utilities used by streaming orchestrators and phases.""" + +from __future__ import annotations + +from typing import Any + + +def resume_step_prefix(turn_id: str) -> str: + """Per-turn ``step_prefix`` for resume invocations. + + Each ``stream_agent_events`` call constructs a fresh + ``AgentEventRelayState`` with ``thinking_step_counter=0``, so two consecutive + resume turns would otherwise both emit ``thinking-resume-1``, ``-2`` etc. + The frontend rehydrates ``currentThinkingSteps`` from the immediate prior + assistant message at the start of every resume — if the new stream's IDs + collide with the seeded ones, React renders sibling Timeline rows with the + same key. Salting with ``turn_id`` guarantees disjoint IDs across resumes + within one thread. + """ + return f"thinking-resume-{turn_id}" + + +def safe_float(value: Any, default: float = 0.0) -> float: + try: + return float(value) + except (TypeError, ValueError): + return default From c13beae1ceea2e344757baea13247d111cf3bd3b Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:08 +0200 Subject: [PATCH 002/133] refactor(chat): add streaming/context/ for mentioned-docs and deep-agents todos Extracts two pure context helpers used during input-state assembly: * mentioned_docs.format_mentioned_surfsense_docs_as_context: renders the user's @-mentioned SurfSense docs into the LLM context block. * deepagents_todos.extract_todos_from_deepagents: pulls the in-progress todo list from a deep-agents state snapshot for the title generator. Add-only; existing call sites in stream_new_chat.py remain untouched until cutover. --- .../tasks/chat/streaming/context/__init__.py | 15 +++++ .../streaming/context/deepagents_todos.py | 27 +++++++++ .../chat/streaming/context/mentioned_docs.py | 58 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/context/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py diff --git a/surfsense_backend/app/tasks/chat/streaming/context/__init__.py b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py new file mode 100644 index 000000000..f858a6c06 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py @@ -0,0 +1,15 @@ +"""Pre-agent context shaping: mentioned-doc rendering and todos extraction.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.context.deepagents_todos import ( + extract_todos_from_deepagents, +) +from app.tasks.chat.streaming.context.mentioned_docs import ( + format_mentioned_surfsense_docs_as_context, +) + +__all__ = [ + "extract_todos_from_deepagents", + "format_mentioned_surfsense_docs_as_context", +] diff --git a/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py new file mode 100644 index 000000000..0bbf4f0a5 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py @@ -0,0 +1,27 @@ +"""Extract todos from a deepagents ``TodoListMiddleware`` ``Command`` output.""" + +from __future__ import annotations + +from typing import Any + + +def extract_todos_from_deepagents(command_output: Any) -> dict: + """Normalize todos out of a deepagents ``Command`` or dict payload. + + deepagents returns a ``Command`` whose ``update['todos']`` is a list of + ``{'content': str, 'status': str}`` dicts. The UI expects the same shape, + so no transformation is required — only extraction. + """ + todos_data: list = [] + if hasattr(command_output, "update"): + update = command_output.update + todos_data = update.get("todos", []) + elif isinstance(command_output, dict): + if "todos" in command_output: + todos_data = command_output.get("todos", []) + elif "update" in command_output and isinstance( + command_output["update"], dict + ): + todos_data = command_output["update"].get("todos", []) + + return {"todos": todos_data} diff --git a/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py b/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py new file mode 100644 index 000000000..e02e98d34 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py @@ -0,0 +1,58 @@ +"""Render user-mentioned SurfSense docs as XML context for the agent.""" + +from __future__ import annotations + +import json + +from app.db import SurfsenseDocsDocument +from app.utils.surfsense_docs import surfsense_docs_public_url + + +def format_mentioned_surfsense_docs_as_context( + documents: list[SurfsenseDocsDocument], +) -> str: + if not documents: + return "" + + context_parts = [""] + context_parts.append( + "The user has explicitly mentioned the following SurfSense documentation pages. " + "These are official documentation about how to use SurfSense and should be used to answer questions about the application. " + "Use [citation:CHUNK_ID] format for citations (e.g., [citation:doc-123])." + ) + + for doc in documents: + public_url = surfsense_docs_public_url(doc.source) + metadata_json = json.dumps( + {"source": doc.source, "public_url": public_url}, ensure_ascii=False + ) + + context_parts.append("") + context_parts.append("") + context_parts.append(f" doc-{doc.id}") + context_parts.append(" SURFSENSE_DOCS") + context_parts.append(f" <![CDATA[{doc.title}]]>") + context_parts.append(f" ") + context_parts.append( + f" " + ) + context_parts.append("") + context_parts.append("") + context_parts.append("") + + if hasattr(doc, "chunks") and doc.chunks: + for chunk in doc.chunks: + context_parts.append( + f" " + ) + else: + context_parts.append( + f" " + ) + + context_parts.append("") + context_parts.append("") + context_parts.append("") + + context_parts.append("") + return "\n".join(context_parts) From 88a58f6aff8f7bcd4b1b5191784ceaf5ab0de57e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:14 +0200 Subject: [PATCH 003/133] refactor(chat): add streaming/contract/ for file-write contract enforcement Extracts the desktop_local_folder file-operation contract helpers: * contract_enforcement_active: gates the contract on filesystem mode. * evaluate_file_contract_outcome: scores tool outputs as success/no-op. * log_file_contract: structured logging of contract verdicts. This is the unit responsible for catching agents that claim to have written/edited a file without actually invoking the filesystem tool. Add-only; stream_new_chat.py keeps its inline duplicates until cutover. --- .../tasks/chat/streaming/contract/__init__.py | 15 ++++++ .../chat/streaming/contract/file_contract.py | 53 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/contract/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py diff --git a/surfsense_backend/app/tasks/chat/streaming/contract/__init__.py b/surfsense_backend/app/tasks/chat/streaming/contract/__init__.py new file mode 100644 index 000000000..4562b362c --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/contract/__init__.py @@ -0,0 +1,15 @@ +"""File-operation contract evaluation and logging.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.contract.file_contract import ( + contract_enforcement_active, + evaluate_file_contract_outcome, + log_file_contract, +) + +__all__ = [ + "contract_enforcement_active", + "evaluate_file_contract_outcome", + "log_file_contract", +] diff --git a/surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py b/surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py new file mode 100644 index 000000000..f21f5da02 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py @@ -0,0 +1,53 @@ +"""File-operation contract: when to enforce, how to score, how to log.""" + +from __future__ import annotations + +import json +from typing import Any + +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.utils.perf import get_perf_logger + +_perf_log = get_perf_logger() + + +def contract_enforcement_active(result: StreamResult) -> bool: + # Enforce only in desktop local-folder mode. Kept deterministic, no + # env-driven progression modes. + return result.filesystem_mode == "desktop_local_folder" + + +def evaluate_file_contract_outcome(result: StreamResult) -> tuple[bool, str]: + if result.intent_detected != "file_write": + return True, "" + if not result.write_attempted: + return False, "no_write_attempt" + if not result.write_succeeded: + return False, "write_failed" + if not result.verification_succeeded: + return False, "verification_failed" + return True, "" + + +def log_file_contract(stage: str, result: StreamResult, **extra: Any) -> None: + payload: dict[str, Any] = { + "stage": stage, + "request_id": result.request_id or "unknown", + "turn_id": result.turn_id or "unknown", + "chat_id": ( + result.turn_id.split(":", 1)[0] if ":" in result.turn_id else "unknown" + ), + "filesystem_mode": result.filesystem_mode, + "client_platform": result.client_platform, + "intent_detected": result.intent_detected, + "intent_confidence": result.intent_confidence, + "write_attempted": result.write_attempted, + "write_succeeded": result.write_succeeded, + "verification_succeeded": result.verification_succeeded, + "commit_gate_passed": result.commit_gate_passed, + "commit_gate_reason": result.commit_gate_reason or None, + } + payload.update(extra) + _perf_log.info( + "[file_operation_contract] %s", json.dumps(payload, ensure_ascii=False) + ) From 94bc827252ad20f0335474981eb1009780f25695 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:20 +0200 Subject: [PATCH 004/133] refactor(chat): add streaming/agent/ package with build_main_agent_for_thread Extracts the agent-construction wrapper that the chat streamers call to materialize the LangGraph agent for a given thread. Centralizes how we pass the agent factory plus checkpointer, runtime context, and the in-memory content builder. Add-only; pre-existing inline equivalent in stream_new_chat.py stays until cutover. --- .../tasks/chat/streaming/agent/__init__.py | 8 +++ .../app/tasks/chat/streaming/agent/builder.py | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/agent/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/agent/builder.py diff --git a/surfsense_backend/app/tasks/chat/streaming/agent/__init__.py b/surfsense_backend/app/tasks/chat/streaming/agent/__init__.py new file mode 100644 index 000000000..260dcb3f2 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/agent/__init__.py @@ -0,0 +1,8 @@ +"""Agent construction and per-turn event-loop drivers.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.agent.builder import build_main_agent_for_thread +from app.tasks.chat.streaming.agent.event_loop import stream_agent_events + +__all__ = ["build_main_agent_for_thread", "stream_agent_events"] diff --git a/surfsense_backend/app/tasks/chat/streaming/agent/builder.py b/surfsense_backend/app/tasks/chat/streaming/agent/builder.py new file mode 100644 index 000000000..0db42edbf --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/agent/builder.py @@ -0,0 +1,49 @@ +"""Single per-thread agent (re)build path. + +A graph swap mid-turn would corrupt checkpointer state for the same +``thread_id``, so both the initial build and any mid-stream 429 recovery rebuild +must funnel through this single function. +""" + +from __future__ import annotations + +from typing import Any + +from app.agents.new_chat.filesystem_selection import FilesystemSelection +from app.agents.new_chat.llm_config import AgentConfig +from app.db import ChatVisibility +from app.services.connector_service import ConnectorService + + +async def build_main_agent_for_thread( + agent_factory: Any, + *, + llm: Any, + search_space_id: int, + db_session: Any, + connector_service: ConnectorService, + checkpointer: Any, + user_id: str | None, + thread_id: int | None, + agent_config: AgentConfig | None, + firecrawl_api_key: str | None, + thread_visibility: ChatVisibility | None, + filesystem_selection: FilesystemSelection | None, + disabled_tools: list[str] | None = None, + mentioned_document_ids: list[int] | None = None, +) -> Any: + return await agent_factory( + llm=llm, + search_space_id=search_space_id, + db_session=db_session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=thread_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=thread_visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + mentioned_document_ids=mentioned_document_ids, + ) From 26c569467dc08515f71d697bca379f6e7e3cfb8d Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:26 +0200 Subject: [PATCH 005/133] refactor(chat): add streaming/agent/event_loop.stream_agent_events Extracts the inner agent-streaming driver previously inlined as _stream_agent_events in stream_new_chat.py. stream_agent_events drives graph_stream.event_stream.stream_output and, after the agent finishes, performs the post-stream safety-net work: * commit any pending content the agent never explicitly finished * evaluate file-operation contract outcomes and emit the appropriate contract verdict for desktop_local_folder turns This unit is what flows/shared/stream_loop.py wraps in the rate-limit recovery while-loop. Add-only; no existing wiring uses it yet. --- .../tasks/chat/streaming/agent/event_loop.py | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py diff --git a/surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py b/surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py new file mode 100644 index 000000000..b77bd3890 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py @@ -0,0 +1,175 @@ +"""Per-turn agent event-loop driver. + +Drives ``stream_output`` (graph_stream relay) for one agent turn, then runs the +post-stream agent-state inspection: safety-net commit of any staged filesystem +state (in case ``aafter_agent`` was skipped), file-operation contract scoring, +intent classification, and interrupt detection. +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator +from typing import Any + +from app.agents.new_chat.filesystem_selection import FilesystemMode +from app.agents.new_chat.middleware.kb_persistence import ( + commit_staged_filesystem_state, +) +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.streaming.contract.file_contract import ( + contract_enforcement_active, + evaluate_file_contract_outcome, + log_file_contract, +) +from app.tasks.chat.streaming.graph_stream.event_stream import stream_output +from app.tasks.chat.streaming.helpers.interrupt_inspector import ( + all_interrupt_values, +) +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.tasks.chat.streaming.shared.utils import safe_float +from app.utils.perf import get_perf_logger + +_perf_log = get_perf_logger() + + +async def stream_agent_events( + agent: Any, + config: dict[str, Any], + input_data: Any, + streaming_service: VercelStreamingService, + result: StreamResult, + step_prefix: str = "thinking", + initial_step_id: str | None = None, + initial_step_title: str = "", + initial_step_items: list[str] | None = None, + *, + fallback_commit_search_space_id: int | None = None, + fallback_commit_created_by_id: str | None = None, + fallback_commit_filesystem_mode: FilesystemMode = FilesystemMode.CLOUD, + fallback_commit_thread_id: int | None = None, + runtime_context: Any = None, + content_builder: Any | None = None, +) -> AsyncGenerator[str, None]: + """Stream and format ``astream_events`` from the agent. + + Yields SSE-formatted strings; after exhausting, ``result`` carries + ``accumulated_text`` and interrupt state. See ``StreamResult`` for the + side-channel surface populated by the underlying relay. + """ + async for sse in stream_output( + agent=agent, + config=config, + input_data=input_data, + streaming_service=streaming_service, + result=result, + step_prefix=step_prefix, + initial_step_id=initial_step_id, + initial_step_title=initial_step_title, + initial_step_items=initial_step_items, + content_builder=content_builder, + runtime_context=runtime_context, + ): + yield sse + + accumulated_text = result.accumulated_text + + state = await agent.aget_state(config) + state_values = getattr(state, "values", {}) or {} + + # Safety net: if astream_events was cancelled before + # KnowledgeBasePersistenceMiddleware.aafter_agent ran, any staged work + # (dirty_paths / staged_dirs / pending_moves / pending_deletes / + # pending_dir_deletes) is still in the checkpointed state. Run the SAME + # shared commit helper so the turn's writes don't get lost on client + # disconnect, then push the delta back into the graph using ``as_node=...`` + # so reducers fire as if the after_agent hook produced it. + if ( + fallback_commit_filesystem_mode == FilesystemMode.CLOUD + and fallback_commit_search_space_id is not None + and ( + (state_values.get("dirty_paths") or []) + or (state_values.get("staged_dirs") or []) + or (state_values.get("pending_moves") or []) + or (state_values.get("pending_deletes") or []) + or (state_values.get("pending_dir_deletes") or []) + ) + ): + try: + delta = await commit_staged_filesystem_state( + state_values, + search_space_id=fallback_commit_search_space_id, + created_by_id=fallback_commit_created_by_id, + filesystem_mode=fallback_commit_filesystem_mode, + thread_id=fallback_commit_thread_id, + dispatch_events=False, + ) + if delta: + await agent.aupdate_state( + config, + delta, + as_node="KnowledgeBasePersistenceMiddleware.after_agent", + ) + except Exception as exc: + _perf_log.warning("[stream_agent_events] safety-net commit failed: %s", exc) + + contract_state = state_values.get("file_operation_contract") or {} + contract_turn_id = contract_state.get("turn_id") + current_turn_id = config.get("configurable", {}).get("turn_id", "") + intent_value = contract_state.get("intent") + if ( + isinstance(intent_value, str) + and intent_value in ("chat_only", "file_write", "file_read") + and contract_turn_id == current_turn_id + ): + result.intent_detected = intent_value + if ( + isinstance(intent_value, str) + and intent_value in ("chat_only", "file_write", "file_read") + and contract_turn_id != current_turn_id + ): + # Ignore stale intent contracts from previous turns/checkpoints. + result.intent_detected = "chat_only" + result.intent_confidence = ( + safe_float(contract_state.get("confidence"), default=0.0) + if contract_turn_id == current_turn_id + else 0.0 + ) + + if result.intent_detected == "file_write": + result.commit_gate_passed, result.commit_gate_reason = ( + evaluate_file_contract_outcome(result) + ) + if not result.commit_gate_passed and contract_enforcement_active(result): + gate_notice = ( + "I could not complete the requested file write because no successful " + "write_file/edit_file operation was confirmed." + ) + gate_text_id = streaming_service.generate_text_id() + yield streaming_service.format_text_start(gate_text_id) + if content_builder is not None: + content_builder.on_text_start(gate_text_id) + yield streaming_service.format_text_delta(gate_text_id, gate_notice) + if content_builder is not None: + content_builder.on_text_delta(gate_text_id, gate_notice) + yield streaming_service.format_text_end(gate_text_id) + if content_builder is not None: + content_builder.on_text_end(gate_text_id) + yield streaming_service.format_terminal_info(gate_notice, "error") + accumulated_text = gate_notice + else: + result.commit_gate_passed = True + result.commit_gate_reason = "" + + result.accumulated_text = accumulated_text + log_file_contract("turn_outcome", result) + + pending_values = all_interrupt_values(state) + if pending_values: + result.is_interrupted = True + # One frame per paused subagent so each parallel HITL renders its own + # approval card on the wire. Order matches ``state.interrupts``, which + # the resume slicer in + # ``checkpointed_subagent_middleware.resume_routing`` consumes in the + # same order — keeping emit and resume in lock-step. + for interrupt_value in pending_values: + yield streaming_service.format_interrupt_request(interrupt_value) From e9a98ecafb6d5f1af3dc8fc2f934e2882da11ec3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:09 +0200 Subject: [PATCH 006/133] refactor(chat): add streaming/flows/shared/ base helpers Six small, single-purpose modules shared by the upcoming new_chat and resume_chat orchestrators: * llm_bundle: dispatches negative config_id to the YAML loader and non-negative config_id to the DB loader, returning (llm, AgentConfig). * pre_stream_setup: builds the connector service, resolves the Firecrawl API key, and returns the chat checkpointer. * first_frames: iter_initial_frames + iter_final_frames emit the canonical message-start / step-start / idle / finish / done SSE envelope. * finalize_emit: iter_token_usage_frame emits the per-turn usage frame from a TokenAccumulator summary. * finally_cleanup: close_session_and_clear_ai_responding and run_gc_pass centralize the finally-block bookkeeping. * span: open_chat_request_span / set_agent_mode / close_chat_request_span / record_outcome_attrs wrap the OpenTelemetry chat_request span. Add-only; these are not yet wired into stream_new_chat.py. --- .../chat/streaming/flows/shared/__init__.py | 3 + .../streaming/flows/shared/finalize_emit.py | 54 +++++++++++++ .../streaming/flows/shared/finally_cleanup.py | 69 ++++++++++++++++ .../streaming/flows/shared/first_frames.py | 40 ++++++++++ .../chat/streaming/flows/shared/llm_bundle.py | 57 +++++++++++++ .../flows/shared/pre_stream_setup.py | 40 ++++++++++ .../tasks/chat/streaming/flows/shared/span.py | 80 +++++++++++++++++++ 7 files changed, 343 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py new file mode 100644 index 000000000..b65acc43c --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py @@ -0,0 +1,3 @@ +"""Building blocks shared by ``new_chat`` and ``resume_chat`` orchestrators.""" + +from __future__ import annotations diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py new file mode 100644 index 000000000..e5de3f6a4 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py @@ -0,0 +1,54 @@ +"""Emit the per-turn token-usage SSE frame from the accumulator. + +``per_message_summary()`` returns ``None`` when the turn made no chargeable +LLM calls (e.g. interrupt-on-input). In that case we skip the frame; the +frontend has no usage to render. +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from app.services.new_streaming_service import VercelStreamingService +from app.utils.perf import get_perf_logger + +if TYPE_CHECKING: + from app.services.token_tracking_service import TokenAccumulator + +_perf_log = get_perf_logger() +logger = logging.getLogger(__name__) + + +def iter_token_usage_frame( + streaming_service: VercelStreamingService, + *, + accumulator: TokenAccumulator, + log_label: str, +): + """Yield zero or one ``data: token-usage`` SSE frame. + + Side effect: logs a one-line ``[token_usage] {log_label}: ...`` summary so + cost analysis can grep call/total/cost across all flows. + """ + usage_summary = accumulator.per_message_summary() + _perf_log.info( + "[token_usage] %s: calls=%d total=%d cost_micros=%d summary=%s", + log_label, + len(accumulator.calls), + accumulator.grand_total, + accumulator.total_cost_micros, + usage_summary, + ) + if usage_summary: + yield streaming_service.format_data( + "token-usage", + { + "usage": usage_summary, + "prompt_tokens": accumulator.total_prompt_tokens, + "completion_tokens": accumulator.total_completion_tokens, + "total_tokens": accumulator.grand_total, + "cost_micros": accumulator.total_cost_micros, + "call_details": accumulator.serialized_calls(), + }, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py new file mode 100644 index 000000000..8d425402f --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py @@ -0,0 +1,69 @@ +"""Shared finally-block helpers: session close, GC pass, native-heap trim. + +These are called from inside an ``anyio.CancelScope(shield=True)`` block in +each flow's ``finally`` (Starlette's BaseHTTPMiddleware cancels the scope on +client disconnect; without the shield the very first ``await`` would raise +``CancelledError`` and the rest of cleanup — including ``session.close()`` — +would never run). +""" + +from __future__ import annotations + +import contextlib +import gc +import logging + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.db import shielded_async_session +from app.services.chat_session_state_service import clear_ai_responding +from app.utils.perf import get_perf_logger, log_system_snapshot, trim_native_heap + +_perf_log = get_perf_logger() +logger = logging.getLogger(__name__) + + +async def close_session_and_clear_ai_responding( + session: AsyncSession, chat_id: int +) -> None: + """Rollback + clear AI-responding flag + expunge_all + close. + + On rollback failure we fall back to a fresh shielded session for the flag + clear so a UI is never stuck on "AI is responding…" after a crash. + """ + try: + await session.rollback() + await clear_ai_responding(session, chat_id) + except Exception: + try: + async with shielded_async_session() as fresh_session: + await clear_ai_responding(fresh_session, chat_id) + except Exception: + logger.warning( + "Failed to clear AI responding state for thread %s", chat_id + ) + + with contextlib.suppress(Exception): + session.expunge_all() + + with contextlib.suppress(Exception): + await session.close() + + +def run_gc_pass(*, log_prefix: str, chat_id: int) -> None: + """One full gen0/1/2 pass + native-heap trim + END system snapshot. + + Breaking circular refs held by the agent graph, tools, and LLM wrappers + needs to happen in the caller (set the locals to ``None``) — this just + runs the collector and logs how many objects came back. + """ + collected = gc.collect(0) + gc.collect(1) + gc.collect(2) + if collected: + _perf_log.info( + "[%s] gc.collect() reclaimed %d objects (chat_id=%s)", + log_prefix, + collected, + chat_id, + ) + trim_native_heap() + log_system_snapshot(f"{log_prefix}_END") diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py new file mode 100644 index 000000000..5e568b1e8 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py @@ -0,0 +1,40 @@ +"""Initial SSE frames every flow emits right after pre-stream setup. + +Order matters: ``message_start`` opens the assistant message, ``start_step`` +opens the first thinking step, ``turn-info`` lets the frontend stamp the +correlation id onto the in-flight message, and ``turn-status: busy`` flips the +UI into the streaming state. +""" + +from __future__ import annotations + +from collections.abc import Iterator + +from app.services.new_streaming_service import VercelStreamingService + + +def iter_initial_frames( + streaming_service: VercelStreamingService, + *, + turn_id: str, +) -> Iterator[str]: + """Yield the four canonical opening frames in order. + + ``turn-info`` carries ``chat_turn_id`` so even pure-text turns (which + never produce a tool / action-log event) still teach the frontend the + turn correlation id used for ``appendMessage`` durable storage. + """ + yield streaming_service.format_message_start() + yield streaming_service.format_start_step() + yield streaming_service.format_data("turn-info", {"chat_turn_id": turn_id}) + yield streaming_service.format_data("turn-status", {"status": "busy"}) + + +def iter_final_frames( + streaming_service: VercelStreamingService, +) -> Iterator[str]: + """Yield ``turn-status: idle`` plus the finish/done trailer in order.""" + yield streaming_service.format_data("turn-status", {"status": "idle"}) + yield streaming_service.format_finish_step() + yield streaming_service.format_finish() + yield streaming_service.format_done() diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py new file mode 100644 index 000000000..2f334114c --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py @@ -0,0 +1,57 @@ +"""Load an LLM + AgentConfig bundle for a given config id. + +Handles both code paths uniformly: +- ``config_id >= 0`` → database-backed ``NewLLMConfig`` row (per-user/per-space). +- ``config_id < 0`` → YAML-defined global LLM config (built-in defaults). + +Returns ``(llm, agent_config, error_message)``; on success ``error_message`` is +``None``. The caller emits the friendly SSE error frame. +""" + +from __future__ import annotations + +from typing import Any + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.agents.new_chat.llm_config import ( + AgentConfig, + create_chat_litellm_from_agent_config, + create_chat_litellm_from_config, + load_agent_config, + load_global_llm_config_by_id, +) + + +async def load_llm_bundle( + session: AsyncSession, + *, + config_id: int, + search_space_id: int, +) -> tuple[Any, AgentConfig | None, str | None]: + if config_id >= 0: + loaded_agent_config = await load_agent_config( + session=session, + config_id=config_id, + search_space_id=search_space_id, + ) + if not loaded_agent_config: + return ( + None, + None, + f"Failed to load NewLLMConfig with id {config_id}", + ) + return ( + create_chat_litellm_from_agent_config(loaded_agent_config), + loaded_agent_config, + None, + ) + + loaded_llm_config = load_global_llm_config_by_id(config_id) + if not loaded_llm_config: + return None, None, f"Failed to load LLM config with id {config_id}" + return ( + create_chat_litellm_from_config(loaded_llm_config), + AgentConfig.from_yaml_config(loaded_llm_config), + None, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py new file mode 100644 index 000000000..ec92306dd --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py @@ -0,0 +1,40 @@ +"""Pre-stream setup: connector service, firecrawl key, checkpointer.""" + +from __future__ import annotations + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.agents.new_chat.checkpointer import get_checkpointer +from app.db import SearchSourceConnectorType +from app.services.connector_service import ConnectorService + + +async def setup_connector_and_firecrawl( + session: AsyncSession, + *, + search_space_id: int, +) -> tuple[ConnectorService, str | None]: + """Build the per-turn connector service and pull the firecrawl API key. + + Returns ``(connector_service, firecrawl_api_key)``. ``firecrawl_api_key`` is + ``None`` when no web-crawler connector is configured (the agent simply + skips firecrawl-backed tools in that case). + """ + connector_service = ConnectorService(session, search_space_id=search_space_id) + firecrawl_api_key: str | None = None + webcrawler_connector = await connector_service.get_connector_by_type( + SearchSourceConnectorType.WEBCRAWLER_CONNECTOR, search_space_id + ) + if webcrawler_connector and webcrawler_connector.config: + firecrawl_api_key = webcrawler_connector.config.get("FIRECRAWL_API_KEY") + return connector_service, firecrawl_api_key + + +async def get_chat_checkpointer(): + """Resolve the PostgreSQL checkpointer for persistent conversation memory. + + Thin wrapper around ``app.agents.new_chat.checkpointer.get_checkpointer`` so + flow orchestrators can rely on a streaming-local symbol and we have a hook + point if the checkpointer source ever needs to vary per flow. + """ + return await get_checkpointer() diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py new file mode 100644 index 000000000..1e5169af1 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py @@ -0,0 +1,80 @@ +"""OpenTelemetry chat-request span wrapper for streaming flows.""" + +from __future__ import annotations + +import contextlib +import sys +from typing import Any, Literal + +from app.observability import metrics as ot_metrics +from app.observability import otel as ot + + +def open_chat_request_span( + *, + chat_id: int, + search_space_id: int, + flow: Literal["new", "regenerate", "resume"], + request_id: str | None, + turn_id: str, + filesystem_mode: str, + client_platform: str, + agent_mode: str, +) -> tuple[Any, Any]: + """Open the per-request span; returns ``(span_cm, span)`` for finally-close.""" + span_cm = ot.chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow=flow, + request_id=request_id, + turn_id=turn_id, + filesystem_mode=filesystem_mode, + client_platform=client_platform, + agent_mode=agent_mode, + ) + span = span_cm.__enter__() + return span_cm, span + + +def set_agent_mode(span: Any, agent_mode: str) -> None: + """Tag the span with the resolved agent mode (single / multi).""" + with contextlib.suppress(Exception): + span.set_attribute("agent.mode", agent_mode) + + +def close_chat_request_span( + *, + span_cm: Any, + span: Any, + chat_outcome: str, + chat_agent_mode: str, + flow: Literal["new", "regenerate", "resume"], + chat_error_category: str | None, + duration_seconds: float, +) -> None: + """Record metrics + close the span. Swallows errors (finally-block context).""" + with contextlib.suppress(Exception): + span.set_attribute("chat.outcome", chat_outcome) + ot_metrics.record_chat_request_duration( + duration_seconds * 1000, + flow=flow, + outcome=chat_outcome, + agent_mode=chat_agent_mode, + ) + ot_metrics.record_chat_request_outcome( + flow=flow, + outcome=chat_outcome, + agent_mode=chat_agent_mode, + error_category=chat_error_category, + ) + span_cm.__exit__(*sys.exc_info()) + + +def record_outcome_attrs( + span: Any, *, chat_outcome: str, chat_error_category: str | None +) -> None: + """Stamp outcome + error.category on the span (used in the except branch).""" + with contextlib.suppress(Exception): + span.set_attribute("chat.outcome", chat_outcome) + if chat_error_category is not None: + span.set_attribute("error.category", chat_error_category) From 40300d300a01705e3134c9953ef6008cd322c9e8 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:14 +0200 Subject: [PATCH 007/133] refactor(chat): add streaming/flows/shared/premium_quota.py Centralizes the premium-credits lifecycle for chat turns: * needs_premium_quota: gate check (premium user + non-fallback config). * PremiumReservation: dataclass capturing reservation state + token totals. * reserve_premium / finalize_premium / release_premium: idempotent reservation, commit, and rollback used by the orchestrators. Add-only; legacy stream_new_chat.py keeps its inline quota handling until cutover. --- .../streaming/flows/shared/premium_quota.py | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py new file mode 100644 index 000000000..0ec40d275 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py @@ -0,0 +1,132 @@ +"""Premium credit (USD micro-units) reserve / finalize / release lifecycle. + +Both ``stream_new_chat`` and ``stream_resume_chat`` reserve premium credits up +front (so a single LLM call can't run away with the budget), then finalize the +actual provider cost reported by LiteLLM when the turn completes successfully, +or release the reservation on the cancellation / interrupted-without-finalize +paths. + +State is held by the orchestrator as a simple ``PremiumReservation`` tuple +so reservation, fallback-on-denied, finalize, and release can all be reasoned +about from one place. +""" + +from __future__ import annotations + +import logging +import uuid as _uuid +from dataclasses import dataclass +from typing import TYPE_CHECKING +from uuid import UUID + +from app.agents.new_chat.llm_config import AgentConfig +from app.db import shielded_async_session + +if TYPE_CHECKING: + from app.services.token_tracking_service import TokenAccumulator + + +@dataclass +class PremiumReservation: + """Active premium-credit reservation for one turn. + + ``request_id`` is the per-reservation idempotency key (also passed to + ``finalize``/``release`` so racing branches resolve to the same row). + ``reserved_micros`` is the up-front estimate; ``finalize`` debits the + actual cost, ``release`` returns it untouched. + """ + + request_id: str + reserved_micros: int + allowed: bool + + +def needs_premium_quota( + agent_config: AgentConfig | None, user_id: str | None +) -> bool: + return bool(agent_config is not None and user_id and agent_config.is_premium) + + +async def reserve_premium( + *, + agent_config: AgentConfig, + user_id: str, +) -> PremiumReservation: + """Reserve estimated micros up front; returns the reservation handle.""" + from app.services.token_quota_service import ( + TokenQuotaService, + estimate_call_reserve_micros, + ) + + request_id = _uuid.uuid4().hex[:16] + litellm_params = agent_config.litellm_params or {} + base_model = ( + litellm_params.get("base_model") if isinstance(litellm_params, dict) else None + ) or agent_config.model_name or "" + reserve_amount_micros = estimate_call_reserve_micros( + base_model=base_model, + quota_reserve_tokens=agent_config.quota_reserve_tokens, + ) + async with shielded_async_session() as quota_session: + quota_result = await TokenQuotaService.premium_reserve( + db_session=quota_session, + user_id=UUID(user_id), + request_id=request_id, + reserve_micros=reserve_amount_micros, + ) + return PremiumReservation( + request_id=request_id, + reserved_micros=reserve_amount_micros, + allowed=quota_result.allowed, + ) + + +async def finalize_premium( + *, + reservation: PremiumReservation, + user_id: str, + accumulator: TokenAccumulator, +) -> None: + """Finalize debit using the actual provider cost reported by LiteLLM. + + Best-effort: failures here must not bubble up to the SSE stream — the user + has already received their tokens; we log and move on. + """ + try: + from app.services.token_quota_service import TokenQuotaService + + async with shielded_async_session() as quota_session: + await TokenQuotaService.premium_finalize( + db_session=quota_session, + user_id=UUID(user_id), + request_id=reservation.request_id, + actual_micros=accumulator.total_cost_micros, + reserved_micros=reservation.reserved_micros, + ) + except Exception: + logging.getLogger(__name__).warning( + "Failed to finalize premium quota for user %s", + user_id, + exc_info=True, + ) + + +async def release_premium( + *, + reservation: PremiumReservation, + user_id: str, +) -> None: + """Release the reservation on cancellation paths; never raises.""" + try: + from app.services.token_quota_service import TokenQuotaService + + async with shielded_async_session() as quota_session: + await TokenQuotaService.premium_release( + db_session=quota_session, + user_id=UUID(user_id), + reserved_micros=reservation.reserved_micros, + ) + except Exception: + logging.getLogger(__name__).warning( + "Failed to release premium quota for user %s", user_id + ) From 2c3edb7c845d1be1a8b4c4d4c772b9ec25945c82 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:18 +0200 Subject: [PATCH 008/133] refactor(chat): add streaming/flows/shared/terminal_error.py Extracts handle_terminal_exception: the shared except-branch behavior for the chat orchestrators. Classifies the raised exception, logs the structured chat_stream error event, and emits the terminal-error SSE frame + done sentinel via the streaming service. Add-only; nothing imports it yet. --- .../streaming/flows/shared/terminal_error.py | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py new file mode 100644 index 000000000..c9db2caf2 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py @@ -0,0 +1,120 @@ +"""Handle the ``except Exception`` branch of a streaming flow. + +Classifies the exception, records OpenTelemetry attributes, emits one terminal +error SSE frame and the trailing ``turn-status: idle`` + finish/done frames. + +Used by both ``stream_new_chat`` and ``stream_resume_chat``; flow-specific bits +(label, span, BusyError tracking) are passed by the caller. +""" + +from __future__ import annotations + +import logging +import traceback +from collections.abc import Iterator +from typing import Any, Literal + +from app.agents.new_chat.errors import BusyError +from app.observability import metrics as ot_metrics +from app.observability import otel as ot +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.streaming.errors.classifier import classify_stream_exception +from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error +from app.tasks.chat.streaming.flows.shared.first_frames import iter_final_frames +from app.tasks.chat.streaming.flows.shared.span import record_outcome_attrs + +logger = logging.getLogger(__name__) + + +def handle_terminal_exception( + exc: Exception, + *, + flow: Literal["new", "regenerate", "resume"], + flow_label: str, + log_prefix: str, + streaming_service: VercelStreamingService, + request_id: str | None, + chat_id: int, + search_space_id: int, + user_id: str | None, + chat_span: Any, +) -> tuple[Iterator[str], dict[str, Any]]: + """Classify, log, and produce the SSE frames for a terminal exception. + + Returns ``(frame_iterator, summary)``. ``summary`` carries:: + + - ``busy_error_raised``: bool — caller must skip the lock-release path + when True (caller never acquired the busy mutex). + - ``chat_outcome``: str — span outcome attribute. + - ``chat_error_category``: str — categorized error label for metrics. + """ + busy_error_raised = isinstance(exc, BusyError) + + ( + error_kind, + error_code, + severity, + is_expected, + user_message, + error_extra, + ) = classify_stream_exception(exc, flow_label=flow_label) + chat_outcome = error_code or error_kind or "error" + chat_error_category = ot_metrics.categorize_exception(exc) + record_outcome_attrs( + chat_span, + chat_outcome=chat_outcome, + chat_error_category=chat_error_category, + ) + with __suppress(): + ot.record_error(chat_span, exc) + error_message = f"Error during {flow_label}: {exc!s}" + # Match the original behavior: log full traceback via ``print`` so it lands + # in stderr regardless of the logger config. + print(f"[{log_prefix}] {error_message}") + print(f"[{log_prefix}] Exception type: {type(exc).__name__}") + print(f"[{log_prefix}] Traceback:\n{traceback.format_exc()}") + + def _iter_frames() -> Iterator[str]: + if error_code == "TURN_CANCELLING": + status_payload: dict[str, Any] = {"status": "cancelling"} + if error_extra: + status_payload.update(error_extra) + yield streaming_service.format_data("turn-status", status_payload) + else: + yield streaming_service.format_data("turn-status", {"status": "busy"}) + + yield emit_stream_terminal_error( + streaming_service=streaming_service, + flow=flow, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=user_message, + error_kind=error_kind, + error_code=error_code, + severity=severity, + is_expected=is_expected, + extra=error_extra, + ) + yield from iter_final_frames(streaming_service) + + return ( + _iter_frames(), + { + "busy_error_raised": busy_error_raised, + "chat_outcome": chat_outcome, + "chat_error_category": chat_error_category, + }, + ) + + +def __suppress(): + """Local single-use ``contextlib.suppress(Exception)`` factory. + + Inlined here so callers don't import ``contextlib`` just for the + ``record_error`` call site. + """ + import contextlib + + return contextlib.suppress(Exception) From b54b803dc9a844d74700f3dc27eb00282d63b081 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:27 +0200 Subject: [PATCH 009/133] refactor(chat): add streaming/flows/shared/ rate-limit recovery + stream loop Two cooperating modules that wrap stream_agent_events with in-stream recovery from provider 429s: * rate_limit_recovery: can_recover_provider_rate_limit truth-table guard, reroute_to_next_auto_pin (selects the next eligible auto-pin config and reloads the LLM bundle), log_rate_limit_recovered. * stream_loop: run_stream_loop drives stream_agent_events in a while-True loop, delegating recovery to a flow-supplied RecoverFn callback so new_chat and resume_chat can share the same loop while keeping their own nonlocal state. Add-only; not yet wired into any orchestrator. --- .../flows/shared/rate_limit_recovery.py | 129 ++++++++++++++++++ .../streaming/flows/shared/stream_loop.py | 85 ++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py new file mode 100644 index 000000000..6b3857594 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py @@ -0,0 +1,129 @@ +"""Shared steps for the in-stream provider rate-limit recovery loop. + +Both flows wrap ``run_stream_loop`` with a flow-specific ``recover`` closure; +the *guard*, the *auto-pin reroute*, and the *post-recovery telemetry* are the +same on both sides and live here so behaviour can't drift. + +The orchestrator owns the parts that genuinely diverge: + + * cancelling the title task (new_chat only), + * passing ``mentioned_document_ids`` to ``build_main_agent_for_thread``, + * the log prefix (``stream_new_chat`` vs ``stream_resume``). +""" + +from __future__ import annotations + +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.agents.new_chat.middleware.busy_mutex import end_turn +from app.observability import otel as ot +from app.services.auto_model_pin_service import ( + mark_runtime_cooldown, + resolve_or_get_pinned_llm_config_id, +) +from app.tasks.chat.streaming.errors.classifier import ( + is_provider_rate_limited, + log_chat_stream_error, +) + + +def can_recover_provider_rate_limit( + exc: BaseException, + *, + first_event_seen: bool, + runtime_rate_limit_recovered: bool, + requested_llm_config_id: int, + current_llm_config_id: int, +) -> bool: + """Guard: only the first auto-pin → provider-rate-limited failure recovers. + + All conditions must hold: + + * ``runtime_rate_limit_recovered is False`` — at most one recovery per turn. + * ``requested_llm_config_id == 0`` — caller opted into auto-pin (id=0). + * ``current_llm_config_id < 0`` — currently on a YAML config (the only + kind the auto-pin pool draws from). + * ``first_event_seen is False`` — we haven't sent any SSE to the user yet, + so a silent rebuild + retry is invisible. + * The exception is provider-side rate-limited (HTTP 429 or known shape). + """ + return ( + not runtime_rate_limit_recovered + and requested_llm_config_id == 0 + and current_llm_config_id < 0 + and not first_event_seen + and is_provider_rate_limited(exc) + ) + + +async def reroute_to_next_auto_pin( + session: AsyncSession, + *, + chat_id: int, + search_space_id: int, + user_id: str | None, + current_llm_config_id: int, + requires_image_input: bool, +) -> int: + """Release lock, cool down the failing config, pick a new auto-pin id. + + Returns the new ``llm_config_id``. ``end_turn`` is called because the failed + attempt may still hold the per-thread busy mutex (middleware teardown can + lag behind raised provider errors) — the same-request retry would otherwise + bounce on ``BusyError``. + """ + end_turn(str(chat_id)) + mark_runtime_cooldown(current_llm_config_id, reason="provider_rate_limited") + pinned = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=0, + exclude_config_ids={current_llm_config_id}, + requires_image_input=requires_image_input, + ) + return pinned.resolved_llm_config_id + + +def log_rate_limit_recovered( + *, + flow: Literal["new", "regenerate", "resume"], + request_id: str | None, + chat_id: int, + search_space_id: int, + user_id: str | None, + previous_config_id: int, + new_config_id: int, +) -> None: + """Emit the OTEL event + structured ``[chat_stream_error]`` log line.""" + ot.add_event( + "chat.rate_limit.recovered", + { + "recovery.reason": "provider_rate_limited", + "recovery.previous_config_id": previous_config_id, + "recovery.fallback_config_id": new_config_id, + }, + ) + log_chat_stream_error( + flow=flow, + error_kind="rate_limited", + error_code="RATE_LIMITED", + severity="info", + is_expected=True, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=( + "Auto-pinned model hit runtime rate limit; switched to " + "another eligible model and retried." + ), + extra={ + "auto_runtime_recover": True, + "previous_config_id": previous_config_id, + "fallback_config_id": new_config_id, + }, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py new file mode 100644 index 000000000..6cf0df855 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py @@ -0,0 +1,85 @@ +"""Drive ``stream_agent_events`` with in-stream rate-limit recovery. + +Both ``stream_new_chat`` and ``stream_resume_chat`` wrap the agent event loop +in a ``while True`` that catches the *first* provider rate-limit error +(``can_runtime_recover``) before any SSE event reaches the user, rebuilds the +agent on an alternative auto-pin, and retries the turn. + +The recovery callback is flow-specific (different ``mentioned_document_ids`` +contract, different logging label, etc.) — this module owns the loop shape, +the caller owns the rebuild. +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator, Awaitable, Callable +from typing import Any + +from app.agents.new_chat.filesystem_selection import FilesystemMode +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.streaming.agent.event_loop import stream_agent_events +from app.tasks.chat.streaming.shared.stream_result import StreamResult + +# Returns the rebuilt agent on a successful recovery, or ``None`` to re-raise +# the original exception (and let the orchestrator's terminal-error path +# handle it). +RecoverFn = Callable[[BaseException, bool], Awaitable[Any | None]] + + +async def run_stream_loop( + *, + agent: Any, + streaming_service: VercelStreamingService, + config: dict[str, Any], + input_data: Any, + stream_result: StreamResult, + step_prefix: str = "thinking", + initial_step_id: str | None = None, + initial_step_title: str = "", + initial_step_items: list[str] | None = None, + fallback_commit_search_space_id: int | None, + fallback_commit_created_by_id: str | None, + fallback_commit_filesystem_mode: FilesystemMode, + fallback_commit_thread_id: int | None, + runtime_context: Any, + content_builder: Any | None, + recover: RecoverFn, + on_first_event: Callable[[], None] | None = None, +) -> AsyncGenerator[str, None]: + """Yield SSE frames; rebuild and retry once on a pre-first-event rate limit. + + ``on_first_event`` fires after the first frame is observed (used by both + flows to write a one-time ``First agent event in N.NNNs`` perf line). + """ + first_event_logged = False + while True: + try: + async for sse in stream_agent_events( + agent=agent, + config=config, + input_data=input_data, + streaming_service=streaming_service, + result=stream_result, + step_prefix=step_prefix, + initial_step_id=initial_step_id, + initial_step_title=initial_step_title, + initial_step_items=initial_step_items, + fallback_commit_search_space_id=fallback_commit_search_space_id, + fallback_commit_created_by_id=fallback_commit_created_by_id, + fallback_commit_filesystem_mode=fallback_commit_filesystem_mode, + fallback_commit_thread_id=fallback_commit_thread_id, + runtime_context=runtime_context, + content_builder=content_builder, + ): + if not first_event_logged: + if on_first_event is not None: + on_first_event() + first_event_logged = True + yield sse + return + except Exception as exc: + new_agent = await recover(exc, first_event_logged) + if new_agent is None: + raise + agent = new_agent + continue From 21bddc73a75c121cc33ee5880d54e618d4409c9d Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:31 +0200 Subject: [PATCH 010/133] refactor(chat): add streaming/flows/shared/assistant_finalize.py Extracts finalize_assistant_message: the post-stream server-side write of the final assistant message (with content parts + token usage) guarded by asyncio.shield + shielded_async_session so a client disconnect cannot abort the persist. Add-only; legacy stream_new_chat.py keeps its inline finalize block until cutover. --- .../flows/shared/assistant_finalize.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py new file mode 100644 index 000000000..d16f81ac7 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py @@ -0,0 +1,109 @@ +"""Server-side assistant-message + token_usage finalization. + +Runs inside the streaming flow's ``finally`` block, after the main session has +been closed (uses its own shielded session, so we don't fight the same DB +connection). + +Idempotent against the legacy frontend ``appendMessage`` recovery branch: + + * the assistant row was already INSERTed by ``persist_assistant_shell`` + earlier in the turn, so this just UPDATEs it with the rich + ``ContentPart[]`` projection from the builder. + * ``token_usage`` uses ``INSERT ... ON CONFLICT DO NOTHING`` against the + partial unique index from migration 142, so a racing append_message + recovery branch can never double-write. + +``mark_interrupted`` closes any open text/reasoning blocks and flips running +tool-calls (no result) to ``state=aborted`` so the persisted JSONB reflects a +coherent end-state even on client disconnect. + +Never raises (best-effort, logs only). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.utils.perf import get_perf_logger + +if TYPE_CHECKING: + from app.services.token_tracking_service import TokenAccumulator + +_perf_log = get_perf_logger() + + +async def finalize_assistant_message( + *, + stream_result: StreamResult | None, + chat_id: int, + search_space_id: int, + user_id: str | None, + accumulator: TokenAccumulator, + log_prefix: str, +) -> None: + """Snapshot the content builder and persist the final assistant payload. + + No-op when ``stream_result`` was never populated, the turn never reached + ``persist_assistant_shell`` (no ``assistant_message_id``), or the turn id + was never assigned. + """ + if not ( + stream_result + and stream_result.turn_id + and stream_result.assistant_message_id + ): + return + + from app.tasks.chat.persistence import finalize_assistant_turn + + builder_stats: dict[str, int] | None = None + if stream_result.content_builder is not None: + stream_result.content_builder.mark_interrupted() + # Snapshot stats BEFORE ``snapshot()`` deepcopies so the perf log + # records the actual finalised payload (post-mark_interrupted), not + # the live-mutating builder state. + builder_stats = stream_result.content_builder.stats() + content_payload = stream_result.content_builder.snapshot() + else: + # Defensive fallback — we always set the builder alongside + # ``assistant_message_id`` in the orchestrator, so this branch only + # fires if a future refactor ever decouples them. Persist whatever + # accumulated text we captured so the row at least renders. + content_payload = [ + { + "type": "text", + "text": stream_result.accumulated_text or "", + } + ] + + if builder_stats is not None: + _perf_log.info( + "[%s] finalize_payload chat_id=%s " + "message_id=%s parts=%d bytes=%d text=%d " + "reasoning=%d tool_calls=%d " + "tool_calls_completed=%d tool_calls_aborted=%d " + "thinking_step_parts=%d step_separators=%d", + log_prefix, + chat_id, + stream_result.assistant_message_id, + builder_stats["parts"], + builder_stats["bytes"], + builder_stats["text"], + builder_stats["reasoning"], + builder_stats["tool_calls"], + builder_stats["tool_calls_completed"], + builder_stats["tool_calls_aborted"], + builder_stats["thinking_step_parts"], + builder_stats["step_separators"], + ) + + await finalize_assistant_turn( + message_id=stream_result.assistant_message_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + turn_id=stream_result.turn_id, + content=content_payload, + accumulator=accumulator, + ) From 927009745e8fde89c0741e5d4fa503984058c416 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:45 +0200 Subject: [PATCH 011/133] refactor(chat): add streaming/flows/new_chat/ per-concern leaf modules Seven focused modules that the upcoming new_chat orchestrator composes: * auto_pin: resolve_initial_auto_pin selects the initial config (with vision-capable filtering and error classification). * llm_capability: check_image_input_capability blocks routing an image-bearing turn to a known text-only model. * runtime_context: build_new_chat_runtime_context assembles the SurfSenseContextSchema for a new-chat turn. * persistence_spawn: spawn_set_ai_responding_bg, spawn_persist_user_task, spawn_persist_assistant_shell_task, and await_persist_task background the four pre-stream DB writes so they overlap with agent build. * initial_thinking_step: build_initial_thinking_step + iter_initial_thinking_step_frame produce the very first thinking-1 SSE step ("Understanding your request" / "Analyzing referenced content"). * title_gen: spawn_title_task + maybe_emit_title_update + await_pending_title_update background the thread-title generator and interleave its update into the stream when ready. * input_state: build_new_chat_input_state assembles the LangGraph input_state (history bootstrap, mentions resolution, context blocks, human-message construction). The heavy one. Add-only; no orchestrator yet (next commit). --- .../chat/streaming/flows/new_chat/auto_pin.py | 95 +++++++ .../flows/new_chat/initial_thinking_step.py | 95 +++++++ .../streaming/flows/new_chat/input_state.py | 264 ++++++++++++++++++ .../flows/new_chat/llm_capability.py | 62 ++++ .../flows/new_chat/persistence_spawn.py | 129 +++++++++ .../flows/new_chat/runtime_context.py | 38 +++ .../streaming/flows/new_chat/title_gen.py | 237 ++++++++++++++++ 7 files changed, 920 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py new file mode 100644 index 000000000..cb20eb011 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py @@ -0,0 +1,95 @@ +"""Resolve the auto-pin for the *initial* turn config. + +Auto-pin (``selected_llm_config_id=0``) picks the best eligible LLM config for +this thread / search space / user, optionally filtered to vision-capable +configs when the turn carries images. + +Errors classified here: + + * ``MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT`` — the auto-pin pool has no + vision-capable cfg for an image-bearing turn. The same gate fires later + in ``llm_capability`` for explicit selections; mapping both to the same + code keeps the FE error UI consistent. + * ``SERVER_ERROR`` — any other ``ValueError`` from the resolver. + +This module owns *initial* pin resolution; the rate-limit recovery loop has +its own narrower auto-pin call (with ``exclude_config_ids``) in +``flows/shared/rate_limit_recovery``. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.observability import otel as ot +from app.services.auto_model_pin_service import resolve_or_get_pinned_llm_config_id + + +@dataclass +class AutoPinResult: + """Outcome of ``resolve_initial_auto_pin``. + + ``llm_config_id`` is set when ``error`` is ``None``; ``error`` carries the + classified user-facing message plus error code/kind so the orchestrator can + emit one terminal-error SSE frame. + """ + + llm_config_id: int | None + error: tuple[str, str, Literal["user_error", "server_error"]] | None + + +async def resolve_initial_auto_pin( + session: AsyncSession, + *, + chat_id: int, + search_space_id: int, + user_id: str | None, + selected_llm_config_id: int, + requires_image_input: bool, + requested_llm_config_id: int, +) -> AutoPinResult: + """Run the resolver and classify any ``ValueError`` for the SSE error path.""" + try: + pinned = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=selected_llm_config_id, + requires_image_input=requires_image_input, + ) + ot.add_event( + "model.pin.resolved", + { + "pin.requested_id": requested_llm_config_id, + "pin.resolved_id": pinned.resolved_llm_config_id, + "pin.requires_image_input": requires_image_input, + }, + ) + return AutoPinResult( + llm_config_id=pinned.resolved_llm_config_id, error=None + ) + except ValueError as pin_error: + # The "no vision-capable cfg" path raises a ValueError whose message + # we map to the friendly image-input SSE error so the user sees the + # same message regardless of whether the gate fired in the resolver or + # in ``llm_capability.assert_vision_capability_for_image_turn``. + is_vision_failure = ( + requires_image_input and "vision-capable" in str(pin_error) + ) + error_code = ( + "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" + if is_vision_failure + else "SERVER_ERROR" + ) + error_kind: Literal["user_error", "server_error"] = ( + "user_error" if is_vision_failure else "server_error" + ) + if is_vision_failure: + ot.add_event("quota.denied", {"quota.code": error_code}) + return AutoPinResult( + llm_config_id=None, error=(str(pin_error), error_code, error_kind) + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py new file mode 100644 index 000000000..c860e517e --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py @@ -0,0 +1,95 @@ +"""Build and emit the first ``thinking-1`` step for a new-chat turn. + +The step title and "Processing X" items are derived from what the user sent +(text snippet, image count, mentioned doc titles) so the FE can render a +meaningful placeholder while the agent stream warms up. + +``thinking-1`` is the canonical id for this step — every subsequent +``thinking-N`` produced by ``stream_agent_events`` folds into the same +singleton ``data-thinking-steps`` part on the FE. +""" + +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass +from typing import Any + +from app.db import SurfsenseDocsDocument +from app.services.new_streaming_service import VercelStreamingService + + +@dataclass +class InitialThinkingStep: + """Resolved fields passed both into the SSE frame and the builder hook. + + ``items`` is the bullet list under the step title; ``title`` is the + one-line step header. ``step_id`` is hard-coded ``thinking-1`` so the FE + Timeline can de-duplicate against the prior assistant message on resume. + """ + + step_id: str + title: str + items: list[str] + + +def build_initial_thinking_step( + *, + user_query: str, + user_image_data_urls: list[str] | None, + mentioned_surfsense_docs: list[SurfsenseDocsDocument], +) -> InitialThinkingStep: + if mentioned_surfsense_docs: + title = "Analyzing referenced content" + action_verb = "Analyzing" + else: + title = "Understanding your request" + action_verb = "Processing" + + processing_parts: list[str] = [] + if user_query.strip(): + query_text = user_query[:80] + ("..." if len(user_query) > 80 else "") + processing_parts.append(query_text) + elif user_image_data_urls: + processing_parts.append(f"[{len(user_image_data_urls)} image(s)]") + else: + processing_parts.append("(message)") + + if mentioned_surfsense_docs: + doc_names: list[str] = [] + for doc in mentioned_surfsense_docs: + t = doc.title + if len(t) > 30: + t = t[:27] + "..." + doc_names.append(t) + if len(doc_names) == 1: + processing_parts.append(f"[{doc_names[0]}]") + else: + processing_parts.append(f"[{len(doc_names)} docs]") + + items = [f"{action_verb}: {' '.join(processing_parts)}"] + return InitialThinkingStep(step_id="thinking-1", title=title, items=items) + + +def iter_initial_thinking_step_frame( + step: InitialThinkingStep, + *, + streaming_service: VercelStreamingService, + content_builder: Any | None, +) -> Iterator[str]: + """Drive both the SSE emission and the builder hook for the initial step. + + The FE folds this step into the same singleton ``data-thinking-steps`` part + as everything the agent stream emits later, so we mirror that fold + server-side by driving the builder lifecycle ourselves. + """ + if content_builder is not None: + content_builder.on_thinking_step( + step.step_id, step.title, "in_progress", step.items + ) + yield streaming_service.format_thinking_step( + step_id=step.step_id, + title=step.title, + status="in_progress", + items=step.items, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py new file mode 100644 index 000000000..fb171c244 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py @@ -0,0 +1,264 @@ +r"""Assemble the LangGraph ``input_state`` for the new-chat turn. + +Pipeline: + + 1. **History bootstrap** — only for cloned chats with no LangGraph checkpoint + yet; flips the per-thread ``needs_history_bootstrap`` flag back to False + once the rows are loaded. + 2. **Mentioned SurfSense docs** — eager-load chunks so the formatter has the + full content without a second roundtrip. + 3. **Recent reports** — top 3 by id desc with non-null content, so the LLM + can resolve ``report_id`` for versioning without spelunking history. + 4. **@-mention resolve** (cloud mode) — substitute ``@title`` tokens in the + query with canonical ``\`/documents/...\``` paths the LLM expects. + 5. **Context block render** — XML-wrap surfsense docs + reports, prepend to + the rewritten query, optionally prefix with display name for SEARCH_SPACE + visibility. + 6. **HumanMessage** — multimodal content if images are attached. + +Returns the assembled ``input_state`` dict plus side-channel data the +orchestrator needs downstream (``accepted_folder_ids`` for runtime context; +``mentioned_surfsense_docs`` for the initial thinking step). +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Any + +from langchain_core.messages import HumanMessage +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload + +from app.agents.new_chat.filesystem_selection import FilesystemMode +from app.agents.new_chat.mention_resolver import resolve_mentions, substitute_in_text +from app.db import ( + ChatVisibility, + NewChatThread, + Report, + SurfsenseDocsDocument, +) +from app.tasks.chat.streaming.context.mentioned_docs import ( + format_mentioned_surfsense_docs_as_context, +) +from app.utils.content_utils import bootstrap_history_from_db +from app.utils.user_message_multimodal import build_human_message_content + +logger = logging.getLogger(__name__) + + +@dataclass +class NewChatInputState: + """Everything ``build_new_chat_input_state`` produces. + + ``input_state`` is fed straight to the agent. ``accepted_folder_ids`` + feeds the runtime context (the resolver may have dropped some chips). + ``mentioned_surfsense_docs`` is consumed by the initial thinking-step + builder for the FE placeholder before the agent stream starts. + """ + + input_state: dict[str, Any] + accepted_folder_ids: list[int] + mentioned_surfsense_docs: list[SurfsenseDocsDocument] + + +async def build_new_chat_input_state( + session: AsyncSession, + *, + chat_id: int, + search_space_id: int, + user_query: str, + user_image_data_urls: list[str] | None, + mentioned_document_ids: list[int] | None, + mentioned_surfsense_doc_ids: list[int] | None, + mentioned_folder_ids: list[int] | None, + mentioned_documents: list[dict[str, Any]] | None, + needs_history_bootstrap: bool, + thread_visibility: ChatVisibility, + current_user_display_name: str | None, + filesystem_mode: str, + request_id: str | None, + turn_id: str, +) -> NewChatInputState: + langchain_messages: list[Any] = [] + + if needs_history_bootstrap: + langchain_messages = await bootstrap_history_from_db( + session, chat_id, thread_visibility=thread_visibility + ) + thread_result = await session.execute( + select(NewChatThread).filter(NewChatThread.id == chat_id) + ) + thread = thread_result.scalars().first() + if thread: + thread.needs_history_bootstrap = False + await session.commit() + + mentioned_surfsense_docs: list[SurfsenseDocsDocument] = [] + if mentioned_surfsense_doc_ids: + result = await session.execute( + select(SurfsenseDocsDocument) + .options(selectinload(SurfsenseDocsDocument.chunks)) + .filter(SurfsenseDocsDocument.id.in_(mentioned_surfsense_doc_ids)) + ) + mentioned_surfsense_docs = list(result.scalars().all()) + + # Top 3 reports keyed by id desc (newest first) with content present, + # surfaced inline so the LLM resolves ``report_id`` for versioning without + # digging through conversation history. + recent_reports_result = await session.execute( + select(Report) + .filter( + Report.thread_id == chat_id, + Report.content.isnot(None), + ) + .order_by(Report.id.desc()) + .limit(3) + ) + recent_reports = list(recent_reports_result.scalars().all()) + + agent_user_query, accepted_folder_ids = await _resolve_mentions_for_query( + session, + search_space_id=search_space_id, + user_query=user_query, + filesystem_mode=filesystem_mode, + mentioned_document_ids=mentioned_document_ids, + mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, + mentioned_folder_ids=mentioned_folder_ids, + mentioned_documents=mentioned_documents, + ) + + final_query = _render_query_with_context( + agent_user_query=agent_user_query, + mentioned_surfsense_docs=mentioned_surfsense_docs, + recent_reports=recent_reports, + ) + + if thread_visibility == ChatVisibility.SEARCH_SPACE and current_user_display_name: + final_query = f"**[{current_user_display_name}]:** {final_query}" + + human_content = build_human_message_content( + final_query, list(user_image_data_urls or ()) + ) + langchain_messages.append(HumanMessage(content=human_content)) + + input_state = { + "messages": langchain_messages, + "search_space_id": search_space_id, + "request_id": request_id or "unknown", + "turn_id": turn_id, + } + + return NewChatInputState( + input_state=input_state, + accepted_folder_ids=accepted_folder_ids, + mentioned_surfsense_docs=mentioned_surfsense_docs, + ) + + +async def _resolve_mentions_for_query( + session: AsyncSession, + *, + search_space_id: int, + user_query: str, + filesystem_mode: str, + mentioned_document_ids: list[int] | None, + mentioned_surfsense_doc_ids: list[int] | None, + mentioned_folder_ids: list[int] | None, + mentioned_documents: list[dict[str, Any]] | None, +) -> tuple[str, list[int]]: + r"""Resolve @-mention chips and rewrite the user query to canonical paths. + + Cloud mode only: local-folder mode keeps the legacy ``@title`` text path + (mention support there is a follow-up task — the path scheme is + mount-rooted and the picker UI both need separate work). + + The substitution lands in the returned ``agent_user_query`` ONLY — the + original ``user_query`` (with ``@title`` tokens) flows untouched into + ``persist_user_turn`` so chip rendering on reload still works + (``UserTextPart`` → ``parseMentionSegments`` matches ``@title``, not + ``\`/documents/...\```). It also feeds the human-readable surfaces — SSE + "Processing X" status, auto thread title, memory seed — which all want + what the user typed. + """ + agent_user_query = user_query + accepted_folder_ids: list[int] = [] + + has_any_mention = bool( + mentioned_document_ids + or mentioned_surfsense_doc_ids + or mentioned_folder_ids + or mentioned_documents + ) + if filesystem_mode != FilesystemMode.CLOUD.value or not has_any_mention: + return agent_user_query, accepted_folder_ids + + from app.schemas.new_chat import MentionedDocumentInfo + + chip_objs: list[MentionedDocumentInfo] | None = None + if mentioned_documents: + chip_objs = [] + for raw in mentioned_documents: + if isinstance(raw, MentionedDocumentInfo): + chip_objs.append(raw) + continue + try: + chip_objs.append(MentionedDocumentInfo.model_validate(raw)) + except Exception: + logger.debug( + "stream_new_chat: dropping malformed mention chip %r", raw + ) + + resolved = await resolve_mentions( + session, + search_space_id=search_space_id, + mentioned_documents=chip_objs, + mentioned_document_ids=mentioned_document_ids, + mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, + mentioned_folder_ids=mentioned_folder_ids, + ) + agent_user_query = substitute_in_text(user_query, resolved.token_to_path) + accepted_folder_ids = resolved.mentioned_folder_ids + return agent_user_query, accepted_folder_ids + + +def _render_query_with_context( + *, + agent_user_query: str, + mentioned_surfsense_docs: list[SurfsenseDocsDocument], + recent_reports: list[Report], +) -> str: + """Prepend surfsense-docs + recent-reports XML blocks to the user query.""" + context_parts: list[str] = [] + + if mentioned_surfsense_docs: + context_parts.append( + format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) + ) + + if recent_reports: + report_lines: list[str] = [] + for r in recent_reports: + report_lines.append( + f' - report_id={r.id}, title="{r.title}", ' + f'style="{r.report_style or "detailed"}"' + ) + reports_listing = "\n".join(report_lines) + context_parts.append( + "\n" + "Previously generated reports in this conversation:\n" + f"{reports_listing}\n\n" + "If the user wants to MODIFY, REVISE, UPDATE, or ADD to one of " + "these reports, set parent_report_id to the relevant report_id above.\n" + "If the user wants a completely NEW report on a different topic, " + "leave parent_report_id unset.\n" + "" + ) + + if context_parts: + context = "\n\n".join(context_parts) + return f"{context}\n\n{agent_user_query}" + + return agent_user_query diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py new file mode 100644 index 000000000..ff5a56eec --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py @@ -0,0 +1,62 @@ +"""Vision-capability gate for image-bearing turns. + +Capability safety net for explicit (non-auto-pin) selections: a turn carrying +user-uploaded images cannot be routed to a chat config that LiteLLM's +authoritative model map *explicitly* marks as text-only (``supports_vision`` +set to False). The check is intentionally narrow — it only fires when LiteLLM +is *certain* the model can't accept image input; unknown / unmapped / +vision-capable models pass through. + +Without this guard a known-text-only model would 404 at the provider with +``"No endpoints found that support image input"``, surfacing as an opaque +``SERVER_ERROR`` SSE chunk; failing here lets us return a friendly message that +tells the user what to change. +""" + +from __future__ import annotations + +from app.agents.new_chat.llm_config import AgentConfig +from app.observability import otel as ot + + +def check_image_input_capability( + *, + user_image_data_urls: list[str] | None, + agent_config: AgentConfig | None, +) -> tuple[str, str] | None: + """Return ``(user_message, error_code)`` when the gate trips, else ``None``. + + The caller emits one terminal-error SSE frame on a non-``None`` return. + """ + if not (user_image_data_urls and agent_config is not None): + return None + + from app.services.provider_capabilities import is_known_text_only_chat_model + + agent_litellm_params = agent_config.litellm_params or {} + agent_base_model = ( + agent_litellm_params.get("base_model") + if isinstance(agent_litellm_params, dict) + else None + ) + if not is_known_text_only_chat_model( + provider=agent_config.provider, + model_name=agent_config.model_name, + base_model=agent_base_model, + custom_provider=agent_config.custom_provider, + ): + return None + + model_label = agent_config.config_name or agent_config.model_name or "model" + ot.add_event( + "quota.denied", {"quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT"} + ) + return ( + ( + f"The selected model ({model_label}) does not support " + "image input. Switch to a vision-capable model " + "(e.g. GPT-4o, Claude, Gemini) or remove the image " + "attachment and try again." + ), + "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT", + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py new file mode 100644 index 000000000..9ea5d2ad6 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py @@ -0,0 +1,129 @@ +"""Concurrent persistence tasks spawned right after the initial validation gate. + +These run *during* the rest of the pre-stream setup so we don't serialize +their latency against agent construction. Awaiting them at the SSE message-id +yield sites preserves the ghost-thread protection (the user-row INSERT must +succeed before any LLM streaming begins). + +The ``set_ai_responding`` flag flip runs fully fire-and-forget on its own +shielded session — failures only delay the "AI is responding…" UI flag, not +the response itself. +""" + +from __future__ import annotations + +import asyncio +import logging +from typing import Any +from uuid import UUID + +from app.db import shielded_async_session +from app.services.chat_session_state_service import set_ai_responding +from app.tasks.chat.persistence import ( + persist_assistant_shell, + persist_user_turn, +) + +logger = logging.getLogger(__name__) + + +def spawn_set_ai_responding_bg( + *, + chat_id: int, + user_id: str | None, + background_tasks: set[asyncio.Task[Any]], +) -> None: + """Fire-and-forget: flip the per-thread AI-responding flag on its own session. + + Errors are swallowed and logged — the worst case is a stale UI flag, which + is preferable to delaying the SSE stream behind a flag write. + """ + if not user_id: + return + + async def _bg_set_ai_responding() -> None: + try: + async with shielded_async_session() as s: + await set_ai_responding(s, chat_id, UUID(user_id)) + except Exception: + logger.warning( + "set_ai_responding failed (chat_id=%s)", + chat_id, + exc_info=True, + ) + + t = asyncio.create_task(_bg_set_ai_responding()) + background_tasks.add(t) + t.add_done_callback(background_tasks.discard) + + +def spawn_persist_user_task( + *, + chat_id: int, + user_id: str | None, + turn_id: str, + user_query: str, + user_image_data_urls: list[str] | None, + mentioned_documents: list[dict[str, Any]] | None, + background_tasks: set[asyncio.Task[Any]], +) -> asyncio.Task[int | None]: + """Spawn the user-row INSERT; await at the user-message-id yield site.""" + task = asyncio.create_task( + persist_user_turn( + chat_id=chat_id, + user_id=user_id, + turn_id=turn_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_documents=mentioned_documents, + ) + ) + background_tasks.add(task) + task.add_done_callback(background_tasks.discard) + return task + + +def spawn_persist_assistant_shell_task( + *, + chat_id: int, + user_id: str | None, + turn_id: str, + background_tasks: set[asyncio.Task[Any]], +) -> asyncio.Task[int | None]: + """Spawn the assistant-shell INSERT; await at the assistant-message-id yield site.""" + task = asyncio.create_task( + persist_assistant_shell( + chat_id=chat_id, + user_id=user_id, + turn_id=turn_id, + ) + ) + background_tasks.add(task) + task.add_done_callback(background_tasks.discard) + return task + + +async def await_persist_task( + task: asyncio.Task[int | None] | None, + *, + chat_id: int, + turn_id: str, + log_label: str, +) -> int | None: + """Join a spawned persistence task with ``shield`` + uniform error handling. + + ``shield`` keeps the DB write alive if the SSE generator is cancelled by + client disconnect mid-await. Returns ``None`` on failure; the caller + abort-paths the turn with a friendly error SSE. + """ + if task is None: + return None + try: + return await asyncio.shield(task) + except asyncio.CancelledError: + raise + except Exception: + logger.exception( + "%s failed (chat_id=%s, turn_id=%s)", log_label, chat_id, turn_id + ) + return None diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py new file mode 100644 index 000000000..1f11be1fe --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py @@ -0,0 +1,38 @@ +"""Build the per-invocation ``SurfSenseContextSchema`` for a new-chat turn. + +Carries the per-turn read inputs that middlewares read via +``runtime.context.*`` instead of from their ``__init__`` closures, so the same +compiled-agent instance can serve multiple turns with different +mention lists / request ids / turn ids without rebuilding the graph. +""" + +from __future__ import annotations + +from app.agents.new_chat.context import SurfSenseContextSchema + + +def build_new_chat_runtime_context( + *, + search_space_id: int, + mentioned_document_ids: list[int] | None, + accepted_folder_ids: list[int], + mentioned_folder_ids: list[int] | None, + request_id: str | None, + turn_id: str, +) -> SurfSenseContextSchema: + """``mentioned_document_ids`` is consumed by ``KnowledgePriorityMiddleware``. + + ``accepted_folder_ids`` (post-resolve) wins over the raw + ``mentioned_folder_ids`` from the request: the resolver drops chips that + pointed at deleted folders or folders the caller can't see, so middlewares + only get authorized ids. + """ + return SurfSenseContextSchema( + search_space_id=search_space_id, + mentioned_document_ids=list(mentioned_document_ids or []), + mentioned_folder_ids=list( + accepted_folder_ids or mentioned_folder_ids or [] + ), + request_id=request_id, + turn_id=turn_id, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py new file mode 100644 index 000000000..11312110f --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py @@ -0,0 +1,237 @@ +"""Background thread-title generation (first-response only). + +The first assistant response in a thread gets a short auto-generated title +inserted into ``new_chat_threads.title``. We: + + 1. Spawn the generation as an ``asyncio.Task`` so it runs in parallel with + the agent stream (no extra TTFT). + 2. Probe inside the task (on its own shielded session) whether this is + actually the first response — newer turns short-circuit to ``None``. + 3. Inject the resulting ``thread-title-update`` SSE frame on the first agent + event after the task completes (mid-stream interlock), or right before + the finish frames (post-stream join) if the task hadn't finished yet. + +Usage tokens come directly off the response (LiteLLM's async callback fires +via fire-and-forget ``create_task``, so the ``TokenTrackingCallback`` would +run too late). We also blank the per-task accumulator so the late callback +doesn't double-count. +""" + +from __future__ import annotations + +import asyncio +import logging +from typing import TYPE_CHECKING, Any + +from sqlalchemy.future import select + +from app.db import NewChatMessage, NewChatThread, shielded_async_session +from app.prompts import TITLE_GENERATION_PROMPT +from app.services.new_streaming_service import VercelStreamingService + +if TYPE_CHECKING: + from app.agents.new_chat.llm_config import AgentConfig + from app.services.token_tracking_service import TokenAccumulator + + +logger = logging.getLogger(__name__) + + +def spawn_title_task( + *, + chat_id: int, + user_query: str, + user_image_data_urls: list[str] | None, + assistant_message_id: int | None, + llm: Any, + agent_config: AgentConfig | None, +) -> asyncio.Task[tuple[str | None, dict | None]] | None: + """Spawn ``_generate_title``; returns ``None`` when prerequisites aren't met. + + Title gen is gated on a real ``assistant_message_id`` so a stream that + aborts before persistence can never leave a thread with a title and no + anchoring rows. + """ + if assistant_message_id is None: + return None + return asyncio.create_task( + _generate_title( + chat_id=chat_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + assistant_message_id=assistant_message_id, + llm=llm, + agent_config=agent_config, + ) + ) + + +async def _generate_title( + *, + chat_id: int, + user_query: str, + user_image_data_urls: list[str] | None, + assistant_message_id: int, + llm: Any, + agent_config: AgentConfig | None, +) -> tuple[str | None, dict | None]: + """Probe is-first-response, then call ``acompletion``. Returns ``(title, usage)``.""" + try: + from litellm import acompletion + + from app.services.llm_router_service import LLMRouterService + from app.services.provider_api_base import resolve_api_base + from app.services.token_tracking_service import _turn_accumulator + + # Excludes this turn's own assistant row (pre-written by + # ``persist_assistant_shell``) — without the ``!=`` filter the gate + # would false-negative on every turn after the first. + try: + async with shielded_async_session() as probe_session: + probe_result = await probe_session.execute( + select(NewChatMessage.id) + .filter( + NewChatMessage.thread_id == chat_id, + NewChatMessage.role == "assistant", + NewChatMessage.id != assistant_message_id, + ) + .limit(1) + ) + is_first_response = probe_result.scalars().first() is None + except Exception: + logger.warning( + "[TitleGen] first-response probe failed (chat_id=%s)", + chat_id, + exc_info=True, + ) + return None, None + + if not is_first_response: + return None, None + + _turn_accumulator.set(None) + + title_seed = user_query.strip() or ( + f"[{len(user_image_data_urls or [])} image(s)]" + if user_image_data_urls + else "" + ) + prompt = TITLE_GENERATION_PROMPT.replace( + "{user_query}", title_seed[:500] or "(message)" + ) + messages = [{"role": "user", "content": prompt}] + + if getattr(llm, "model", None) == "auto": + router = LLMRouterService.get_router() + response = await router.acompletion(model="auto", messages=messages) + else: + # Apply the same ``api_base`` cascade chat / vision / image-gen + # call sites use so we never inherit ``litellm.api_base`` + # (commonly set by ``AZURE_OPENAI_ENDPOINT``) when the chat + # config itself ships an empty ``api_base``. Without this the + # title-gen on an OpenRouter chat config would 404 against the + # inherited Azure endpoint — see ``provider_api_base`` for the + # same bug repro on the image-gen / vision paths. + raw_model = getattr(llm, "model", "") or "" + provider_prefix = ( + raw_model.split("/", 1)[0] if "/" in raw_model else None + ) + provider_value = ( + agent_config.provider if agent_config is not None else None + ) + title_api_base = resolve_api_base( + provider=provider_value, + provider_prefix=provider_prefix, + config_api_base=getattr(llm, "api_base", None), + ) + response = await acompletion( + model=raw_model, + messages=messages, + api_key=getattr(llm, "api_key", None), + api_base=title_api_base, + ) + + usage_info = None + usage = getattr(response, "usage", None) + if usage: + raw_model = getattr(llm, "model", "") or "" + model_name = ( + raw_model.split("/", 1)[-1] + if "/" in raw_model + else (raw_model or response.model or "unknown") + ) + usage_info = { + "model": model_name, + "prompt_tokens": getattr(usage, "prompt_tokens", 0) or 0, + "completion_tokens": getattr(usage, "completion_tokens", 0) or 0, + "total_tokens": getattr(usage, "total_tokens", 0) or 0, + } + + raw_title = response.choices[0].message.content.strip() + if raw_title and len(raw_title) <= 100: + return raw_title.strip("\"'"), usage_info + return None, usage_info + except Exception: + logger.exception("[TitleGen] _generate_title failed") + return None, None + + +async def maybe_emit_title_update( + *, + title_task: asyncio.Task[tuple[str | None, dict | None]] | None, + title_emitted: bool, + chat_id: int, + accumulator: TokenAccumulator, + streaming_service: VercelStreamingService, +): + """Inject one ``thread-title-update`` SSE if the task completed. + + Yields the SSE frame (when applicable). Returns nothing; the orchestrator + flips ``title_emitted`` itself after iterating so we don't fight Python's + nonlocal-in-generator semantics. + """ + if title_task is None or title_emitted or not title_task.done(): + return + generated_title, title_usage = title_task.result() + if title_usage: + accumulator.add(**title_usage) + if generated_title: + async with shielded_async_session() as title_session: + title_thread_result = await title_session.execute( + select(NewChatThread).filter(NewChatThread.id == chat_id) + ) + title_thread = title_thread_result.scalars().first() + if title_thread: + title_thread.title = generated_title + await title_session.commit() + yield streaming_service.format_thread_title_update(chat_id, generated_title) + + +async def await_pending_title_update( + *, + title_task: asyncio.Task[tuple[str | None, dict | None]] | None, + title_emitted: bool, + chat_id: int, + accumulator: TokenAccumulator, + streaming_service: VercelStreamingService, +): + """If the task hadn't completed during the stream, await it now and emit. + + Used right before the finish frames in the success path. Mirror of + ``maybe_emit_title_update`` but unconditionally awaits. + """ + if title_task is None or title_emitted: + return + generated_title, title_usage = await title_task + if title_usage: + accumulator.add(**title_usage) + if generated_title: + async with shielded_async_session() as title_session: + title_thread_result = await title_session.execute( + select(NewChatThread).filter(NewChatThread.id == chat_id) + ) + title_thread = title_thread_result.scalars().first() + if title_thread: + title_thread.title = generated_title + await title_session.commit() + yield streaming_service.format_thread_title_update(chat_id, generated_title) From b2a08885887c995034bdb759a43105f326737e47 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:55 +0200 Subject: [PATCH 012/133] refactor(chat): add streaming/flows/new_chat/orchestrator.stream_new_chat Slim composition root for the new-chat streaming flow. Sequences: 1. validate inputs and load the LLM bundle (negative id => YAML) 2. open the OTEL chat_request span; set agent_mode tag 3. spawn the four pre-stream DB writes (set-ai-responding, persist user turn, persist assistant shell, first-assistant probe) 4. reserve premium quota (with free-fallback retry on denial) 5. build connector + checkpointer + agent + input_state 6. emit first frames (message-start, step-start, initial thinking step) 7. spawn the background title generator 8. run the shared stream_loop with a flow-local _recover closure that reroutes to the next auto-pin config on provider 429s 9. finalize: emit terminal title/token frames, shielded assistant finalize, release-or-finalize premium quota, close session, GC, record OTEL outcome Public entry-point flows/new_chat/__init__ re-exports stream_new_chat. Existing wiring (routes, tests) still imports the legacy function from app.tasks.chat.stream_new_chat. Cutover is a later commit. --- .../chat/streaming/flows/new_chat/__init__.py | 12 + .../streaming/flows/new_chat/orchestrator.py | 868 ++++++++++++++++++ 2 files changed, 880 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py new file mode 100644 index 000000000..566d5e0d9 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py @@ -0,0 +1,12 @@ +"""New-chat streaming flow. + +The public entry point ``stream_new_chat`` is the slim coroutine in +``orchestrator.py`` that composes the per-concern modules in this folder and +the building blocks under ``flows/shared/``. +""" + +from __future__ import annotations + +from app.tasks.chat.streaming.flows.new_chat.orchestrator import stream_new_chat + +__all__ = ["stream_new_chat"] diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py new file mode 100644 index 000000000..bca72b5ea --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py @@ -0,0 +1,868 @@ +"""``stream_new_chat`` — public entry point for a fresh chat turn. + +Slim composition layer over the per-concern modules in this folder and the +building blocks under ``flows/shared/``. Each phase corresponds to a numbered +block in the surrounding code so the on-the-wire ordering stays explicit: + + 1. Validation / config — auto-pin, LLM bundle, capability, premium reserve. + 2. Concurrent persistence + pre-stream setup — spawn DB writes, build the + connector, fetch the checkpointer, build the agent. + 3. Input assembly — history bootstrap, mentions, surfsense docs, reports. + 4. First SSE frames — message_start, start_step, turn-info, turn-status. + 5. Persistence join + message-id frames (ghost-thread protection). + 6. Initial thinking step + title task + runtime context. + 7. Stream loop with in-stream rate-limit recovery + mid-stream title emit. + 8. Finalize — premium debit, token-usage SSE, finish frames. + 9. Exception branch — classify, emit terminal error, finish frames. + 10. Finally — premium release, session close, assistant finalize, GC, span. +""" + +from __future__ import annotations + +import asyncio +import contextlib +import logging +import time +from collections.abc import AsyncGenerator +from functools import partial +from typing import Any, Literal + +import anyio + +from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent +from app.agents.new_chat.filesystem_selection import FilesystemMode, FilesystemSelection +from app.agents.new_chat.middleware.busy_mutex import end_turn +from app.config import config as _app_config +from app.db import ChatVisibility, async_session_maker +from app.observability import otel as ot +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.content_builder import AssistantContentBuilder +from app.tasks.chat.streaming.agent.builder import build_main_agent_for_thread +from app.tasks.chat.streaming.contract.file_contract import log_file_contract +from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error +from app.tasks.chat.streaming.flows.new_chat.auto_pin import resolve_initial_auto_pin +from app.tasks.chat.streaming.flows.new_chat.initial_thinking_step import ( + build_initial_thinking_step, + iter_initial_thinking_step_frame, +) +from app.tasks.chat.streaming.flows.new_chat.input_state import ( + build_new_chat_input_state, +) +from app.tasks.chat.streaming.flows.new_chat.llm_capability import ( + check_image_input_capability, +) +from app.tasks.chat.streaming.flows.new_chat.persistence_spawn import ( + await_persist_task, + spawn_persist_assistant_shell_task, + spawn_persist_user_task, + spawn_set_ai_responding_bg, +) +from app.tasks.chat.streaming.flows.new_chat.runtime_context import ( + build_new_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.new_chat.title_gen import ( + await_pending_title_update, + maybe_emit_title_update, + spawn_title_task, +) +from app.tasks.chat.streaming.flows.shared.assistant_finalize import ( + finalize_assistant_message, +) +from app.tasks.chat.streaming.flows.shared.finalize_emit import iter_token_usage_frame +from app.tasks.chat.streaming.flows.shared.finally_cleanup import ( + close_session_and_clear_ai_responding, + run_gc_pass, +) +from app.tasks.chat.streaming.flows.shared.first_frames import ( + iter_final_frames, + iter_initial_frames, +) +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( + get_chat_checkpointer, + setup_connector_and_firecrawl, +) +from app.tasks.chat.streaming.flows.shared.premium_quota import ( + PremiumReservation, + finalize_premium, + needs_premium_quota, + release_premium, + reserve_premium, +) +from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import ( + can_recover_provider_rate_limit, + log_rate_limit_recovered, + reroute_to_next_auto_pin, +) +from app.tasks.chat.streaming.flows.shared.span import ( + close_chat_request_span, + open_chat_request_span, + set_agent_mode, +) +from app.tasks.chat.streaming.flows.shared.stream_loop import run_stream_loop +from app.tasks.chat.streaming.flows.shared.terminal_error import ( + handle_terminal_exception, +) +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.utils.perf import get_perf_logger, log_system_snapshot + +logger = logging.getLogger(__name__) +_perf_log = get_perf_logger() + +# Holds spawned background tasks (set_ai_responding, persist_user, persist_asst) +# so the GC doesn't drop them before they finish. Kept at module level so it +# survives across turns within one process. +_background_tasks: set[asyncio.Task] = set() + + +async def stream_new_chat( + user_query: str, + search_space_id: int, + chat_id: int, + user_id: str | None = None, + llm_config_id: int = -1, + mentioned_document_ids: list[int] | None = None, + mentioned_surfsense_doc_ids: list[int] | None = None, + mentioned_folder_ids: list[int] | None = None, + mentioned_documents: list[dict[str, Any]] | None = None, + checkpoint_id: str | None = None, + needs_history_bootstrap: bool = False, + thread_visibility: ChatVisibility | None = None, + current_user_display_name: str | None = None, + disabled_tools: list[str] | None = None, + filesystem_selection: FilesystemSelection | None = None, + request_id: str | None = None, + user_image_data_urls: list[str] | None = None, + flow: Literal["new", "regenerate"] = "new", +) -> AsyncGenerator[str, None]: + """Stream a new chat turn using the SurfSense deep agent. + + Uses the Vercel AI SDK Data Stream Protocol (SSE). ``chat_id`` is the + LangGraph thread id (durable conversation memory via the checkpointer). + Manages its own database session so cleanup runs even when Starlette + cancels the task on client disconnect. + """ + streaming_service = VercelStreamingService() + stream_result = StreamResult() + _t_total = time.perf_counter() + fs_mode = filesystem_selection.mode.value if filesystem_selection else "cloud" + fs_platform = ( + filesystem_selection.client_platform.value if filesystem_selection else "web" + ) + stream_result.request_id = request_id + stream_result.turn_id = f"{chat_id}:{int(time.time() * 1000)}" + stream_result.filesystem_mode = fs_mode + stream_result.client_platform = fs_platform + + chat_agent_mode = "unknown" + chat_outcome = "success" + chat_error_category: str | None = None + chat_span_cm, chat_span = open_chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow=flow, + request_id=request_id, + turn_id=stream_result.turn_id, + filesystem_mode=fs_mode, + client_platform=fs_platform, + agent_mode=chat_agent_mode, + ) + log_file_contract("turn_start", stream_result) + _perf_log.info( + "[stream_new_chat] filesystem_mode=%s client_platform=%s", + fs_mode, + fs_platform, + ) + log_system_snapshot("stream_new_chat_START") + + from app.services.token_tracking_service import start_turn + + accumulator = start_turn() + + premium_reservation: PremiumReservation | None = None + busy_error_raised = False + + emit_stream_error = partial( + emit_stream_terminal_error, + streaming_service=streaming_service, + flow=flow, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + ) + + session = async_session_maker() + # Declared at function scope so SSE-yield join points and the finally + # clause see them on every exit path. + persist_user_task: asyncio.Task[int | None] | None = None + persist_asst_task: asyncio.Task[int | None] | None = None + try: + spawn_set_ai_responding_bg( + chat_id=chat_id, user_id=user_id, background_tasks=_background_tasks + ) + + # --- Block 1: LLM config + capability --- + + requested_llm_config_id = llm_config_id + requires_image_input = bool(user_image_data_urls) + + _t0 = time.perf_counter() + pin_result = await resolve_initial_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=llm_config_id, + requires_image_input=requires_image_input, + requested_llm_config_id=requested_llm_config_id, + ) + if pin_result.error is not None: + message, error_code, error_kind = pin_result.error + yield emit_stream_error( + message=message, error_kind=error_kind, error_code=error_code + ) + yield streaming_service.format_done() + return + llm_config_id = pin_result.llm_config_id # type: ignore[assignment] + + llm, agent_config, llm_load_error = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + _perf_log.info( + "[stream_new_chat] LLM config loaded in %.3fs (config_id=%s)", + time.perf_counter() - _t0, + llm_config_id, + ) + + capability_error = check_image_input_capability( + user_image_data_urls=user_image_data_urls, agent_config=agent_config + ) + if capability_error is not None: + message, error_code = capability_error + yield emit_stream_error( + message=message, + error_kind="user_error", + error_code=error_code, + ) + yield streaming_service.format_done() + return + + if needs_premium_quota(agent_config, user_id): + premium_reservation = await reserve_premium( + agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + ) + if not premium_reservation.allowed: + ot.add_event("quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"}) + if requested_llm_config_id == 0: + pin_fallback = await resolve_initial_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=0, + requires_image_input=requires_image_input, + requested_llm_config_id=requested_llm_config_id, + ) + if pin_fallback.error is not None: + message, error_code, error_kind = pin_fallback.error + yield emit_stream_error( + message=message, + error_kind=error_kind, + error_code=error_code, + ) + yield streaming_service.format_done() + return + llm_config_id = pin_fallback.llm_config_id # type: ignore[assignment] + ot.add_event( + "model.repin", + { + "repin.reason": "premium_quota_exhausted", + "repin.to_config_id": llm_config_id, + }, + ) + llm, agent_config, llm_load_error = await load_llm_bundle( + session, + config_id=llm_config_id, + search_space_id=search_space_id, + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + premium_reservation = None + # Re-route to free fallback logged via the structured + # stream-error logger so cost/analytics see the auto-switch. + from app.tasks.chat.streaming.errors.classifier import ( + log_chat_stream_error, + ) + + log_chat_stream_error( + flow=flow, + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=( + "Premium quota exhausted on pinned model; " + "auto-fallback switched to a free model" + ), + extra={ + "fallback_config_id": llm_config_id, + "auto_fallback": True, + }, + ) + else: + yield emit_stream_error( + message=( + "Buy more tokens to continue with this model, or " + "switch to a free model" + ), + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + extra={ + "resolved_config_id": llm_config_id, + "auto_fallback": False, + }, + ) + yield streaming_service.format_done() + return + + if not llm: + yield emit_stream_error( + message="Failed to create LLM instance", + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + + # --- Block 2: Spawn concurrent persistence; build pre-stream setup --- + + persist_user_task = spawn_persist_user_task( + chat_id=chat_id, + user_id=user_id, + turn_id=stream_result.turn_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_documents=mentioned_documents, + background_tasks=_background_tasks, + ) + persist_asst_task = spawn_persist_assistant_shell_task( + chat_id=chat_id, + user_id=user_id, + turn_id=stream_result.turn_id, + background_tasks=_background_tasks, + ) + + _t0 = time.perf_counter() + connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( + session, search_space_id=search_space_id + ) + _perf_log.info( + "[stream_new_chat] Connector service + firecrawl key in %.3fs", + time.perf_counter() - _t0, + ) + + _t0 = time.perf_counter() + checkpointer = await get_chat_checkpointer() + _perf_log.info( + "[stream_new_chat] Checkpointer ready in %.3fs", time.perf_counter() - _t0 + ) + + visibility = thread_visibility or ChatVisibility.PRIVATE + use_multi_agent = bool(_app_config.MULTI_AGENT_CHAT_ENABLED) + chat_agent_mode = "multi" if use_multi_agent else "single" + set_agent_mode(chat_span, chat_agent_mode) + + _t0 = time.perf_counter() + agent_factory = ( + create_multi_agent_chat_deep_agent + if use_multi_agent + else create_surfsense_deep_agent + ) + # Build the agent inline. Provider 429s surface through the in-stream + # recovery loop below, which repins the thread to an eligible + # alternative config and rebuilds the agent before the user sees any + # output. + agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + mentioned_document_ids=mentioned_document_ids, + ) + _perf_log.info( + "[stream_new_chat] Agent created in %.3fs", time.perf_counter() - _t0 + ) + + # --- Block 3: Input assembly --- + + _t0 = time.perf_counter() + assembled = await build_new_chat_input_state( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_document_ids=mentioned_document_ids, + mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, + mentioned_folder_ids=mentioned_folder_ids, + mentioned_documents=mentioned_documents, + needs_history_bootstrap=needs_history_bootstrap, + thread_visibility=visibility, + current_user_display_name=current_user_display_name, + filesystem_mode=fs_mode, + request_id=request_id, + turn_id=stream_result.turn_id, + ) + input_state = assembled.input_state + accepted_folder_ids = assembled.accepted_folder_ids + mentioned_surfsense_docs = assembled.mentioned_surfsense_docs + _perf_log.info( + "[stream_new_chat] History bootstrap + doc/report queries in %.3fs", + time.perf_counter() - _t0, + ) + + # All pre-streaming DB reads done. Commit to release the transaction + # and its ACCESS SHARE locks so we don't block DDL (e.g. migrations) + # for the entire LLM streaming duration. Tools that need DB access + # during streaming start their own short-lived transactions (or use + # isolated sessions). + await session.commit() + # Detach heavy ORM objects (documents with chunks, reports, etc.) + # from the session identity map now that we've extracted what we + # need. Without this they accumulate in memory for the entire + # streaming duration (which can be several minutes). + session.expunge_all() + + _perf_log.info( + "[stream_new_chat] Total pre-stream setup in %.3fs (chat_id=%s)", + time.perf_counter() - _t_total, + chat_id, + ) + + configurable: dict[str, Any] = { + "thread_id": str(chat_id), + "request_id": request_id or "unknown", + "turn_id": stream_result.turn_id, + } + if checkpoint_id: + configurable["checkpoint_id"] = checkpoint_id + + config = { + "configurable": configurable, + # Effectively uncapped, matching the agent-level ``with_config`` + # default in ``chat_deepagent.create_agent`` and the unbounded + # ``while(true)`` in OpenCode's ``session/processor.ts``. Real + # circuit-breakers live in middleware (``DoomLoopMiddleware``, + # plus ``enable_tool_call_limit`` / ``enable_model_call_limit``). + # The original 25 (and our previous 80 bump) hit users on + # legitimate multi-tool plans. + "recursion_limit": 10_000, + } + + # --- Block 4: First SSE frames --- + + for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + yield sse + + # --- Block 5: Persistence join + message-id frames --- + + user_message_id = await await_persist_task( + persist_user_task, + chat_id=chat_id, + turn_id=stream_result.turn_id, + log_label="persist_user_task", + ) + if user_message_id is None: + yield emit_stream_error( + message="We couldn't save your message. Please try again in a moment.", + error_kind="server_error", + error_code="MESSAGE_PERSIST_FAILED", + ) + for sse in iter_final_frames(streaming_service): + yield sse + return + + # Emit canonical user message id BEFORE any LLM streaming so the FE + # can rename its optimistic ``msg-user-XXX`` placeholder to + # ``msg-{user_message_id}`` and unlock features gated on a real DB id + # (comments, edit-from-this-message). See B4 in the + # ``sse-based_message_id_handshake`` plan. + yield streaming_service.format_data( + "user-message-id", + {"message_id": user_message_id, "turn_id": stream_result.turn_id}, + ) + + assistant_message_id = await await_persist_task( + persist_asst_task, + chat_id=chat_id, + turn_id=stream_result.turn_id, + log_label="persist_asst_task", + ) + if assistant_message_id is None: + # Genuine DB failure — abort the turn rather than stream into a + # void. The user row is already persisted so the legacy + # ghost-thread gate isn't reopened. + yield emit_stream_error( + message=( + "We couldn't initialize the assistant message. Please try again." + ), + error_kind="server_error", + error_code="MESSAGE_PERSIST_FAILED", + ) + for sse in iter_final_frames(streaming_service): + yield sse + return + + yield streaming_service.format_data( + "assistant-message-id", + {"message_id": assistant_message_id, "turn_id": stream_result.turn_id}, + ) + + stream_result.assistant_message_id = assistant_message_id + stream_result.content_builder = AssistantContentBuilder() + + # --- Block 6: Initial thinking step + title task + runtime context --- + + initial_step = build_initial_thinking_step( + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_surfsense_docs=mentioned_surfsense_docs, + ) + for sse in iter_initial_thinking_step_frame( + initial_step, + streaming_service=streaming_service, + content_builder=stream_result.content_builder, + ): + yield sse + + initial_step_id = initial_step.step_id + initial_step_title = initial_step.title + initial_step_items = initial_step.items + # Drop the heavy ORM objects + the container that holds them so they + # aren't retained for the entire streaming duration. ``input_state`` + # already carries the langchain_messages list independently. + del assembled, mentioned_surfsense_docs + + title_task = spawn_title_task( + chat_id=chat_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + assistant_message_id=assistant_message_id, + llm=llm, + agent_config=agent_config, + ) + title_emitted = False + + runtime_context = build_new_chat_runtime_context( + search_space_id=search_space_id, + mentioned_document_ids=mentioned_document_ids, + accepted_folder_ids=accepted_folder_ids, + mentioned_folder_ids=mentioned_folder_ids, + request_id=request_id, + turn_id=stream_result.turn_id, + ) + + # --- Block 7: Stream loop --- + + _t_stream_start = time.perf_counter() + runtime_rate_limit_recovered = False + + def _on_first_event() -> None: + _perf_log.info( + "[stream_new_chat] First agent event in %.3fs (time since stream start), " + "%.3fs (total since request start) (chat_id=%s)", + time.perf_counter() - _t_stream_start, + time.perf_counter() - _t_total, + chat_id, + ) + + async def _recover(exc: BaseException, first_event_seen: bool): + nonlocal llm_config_id, llm, agent_config, runtime_rate_limit_recovered + nonlocal title_task + if not can_recover_provider_rate_limit( + exc, + first_event_seen=first_event_seen, + runtime_rate_limit_recovered=runtime_rate_limit_recovered, + requested_llm_config_id=requested_llm_config_id, + current_llm_config_id=llm_config_id, + ): + return None + runtime_rate_limit_recovered = True + previous_config_id = llm_config_id + llm_config_id = await reroute_to_next_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + current_llm_config_id=llm_config_id, + requires_image_input=requires_image_input, + ) + new_llm, new_agent_config, llm_load_err = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_err: + # Re-raise the original so the terminal-error path classifies + # it correctly (don't swallow as "config load error"). + return None + llm = new_llm + agent_config = new_agent_config + + # Title gen used the initial llm object. After a runtime repin we + # keep the stream focused on response recovery and skip title gen + # for this turn. + if title_task is not None and not title_task.done(): + title_task.cancel() + title_task = None + + _t_rebuild = time.perf_counter() + new_agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + mentioned_document_ids=mentioned_document_ids, + ) + _perf_log.info( + "[stream_new_chat] Runtime rate-limit recovery repinned " + "config_id=%s -> %s and rebuilt agent in %.3fs", + previous_config_id, + llm_config_id, + time.perf_counter() - _t_rebuild, + ) + log_rate_limit_recovered( + flow=flow, + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + previous_config_id=previous_config_id, + new_config_id=llm_config_id, + ) + return new_agent + + async for sse in run_stream_loop( + agent=agent, + streaming_service=streaming_service, + config=config, + input_data=input_state, + stream_result=stream_result, + step_prefix="thinking", + initial_step_id=initial_step_id, + initial_step_title=initial_step_title, + initial_step_items=initial_step_items, + fallback_commit_search_space_id=search_space_id, + fallback_commit_created_by_id=user_id, + fallback_commit_filesystem_mode=( + filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + ), + fallback_commit_thread_id=chat_id, + runtime_context=runtime_context, + content_builder=stream_result.content_builder, + recover=_recover, + on_first_event=_on_first_event, + ): + yield sse + # Inject the title update mid-stream as soon as the background + # task finishes; gated so we emit at most once. + async for title_sse in maybe_emit_title_update( + title_task=title_task, + title_emitted=title_emitted, + chat_id=chat_id, + accumulator=accumulator, + streaming_service=streaming_service, + ): + yield title_sse + title_emitted = True + # Account for the case where the task completed but produced no + # title — flip the flag anyway so we don't keep checking it. + if ( + title_task is not None + and title_task.done() + and not title_emitted + ): + title_emitted = True + + _perf_log.info( + "[stream_new_chat] Agent stream completed in %.3fs (chat_id=%s)", + time.perf_counter() - _t_stream_start, + chat_id, + ) + log_system_snapshot("stream_new_chat_END") + + # --- Block 8: Finalize --- + + if stream_result.is_interrupted: + ot.add_event("chat.interrupted", {"chat.flow": flow}) + if title_task is not None and not title_task.done(): + title_task.cancel() + for sse in iter_token_usage_frame( + streaming_service, + accumulator=accumulator, + log_label="interrupted new_chat", + ): + yield sse + yield streaming_service.format_finish_step() + yield streaming_service.format_finish() + yield streaming_service.format_done() + return + + async for title_sse in await_pending_title_update( + title_task=title_task, + title_emitted=title_emitted, + chat_id=chat_id, + accumulator=accumulator, + streaming_service=streaming_service, + ): + yield title_sse + + # Finalize premium credit debit with the actual provider cost reported + # by LiteLLM, summed across every call in the turn. Mirrors the + # pre-cost behaviour of "premium turn → all calls count" so free + # sub-agent calls during a premium turn still contribute to the bill + # (they're $0 in practice anyway). + if premium_reservation is not None and user_id: + await finalize_premium( + reservation=premium_reservation, + user_id=user_id, + accumulator=accumulator, + ) + premium_reservation = None + + for sse in iter_token_usage_frame( + streaming_service, accumulator=accumulator, log_label="normal new_chat" + ): + yield sse + + for sse in iter_final_frames(streaming_service): + yield sse + + except Exception as exc: + frames, summary = handle_terminal_exception( + exc, + flow=flow, + flow_label="chat", + log_prefix="stream_new_chat", + streaming_service=streaming_service, + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + chat_span=chat_span, + ) + if summary["busy_error_raised"]: + busy_error_raised = True + chat_outcome = summary["chat_outcome"] + chat_error_category = summary["chat_error_category"] + for sse in frames: + yield sse + + finally: + # Shield the ENTIRE async cleanup from anyio cancel-scope cancellation. + # Starlette's BaseHTTPMiddleware uses anyio task groups; on client + # disconnect, it cancels the scope with level-triggered cancellation + # — every unshielded ``await`` would raise CancelledError immediately. + # Without this the very first ``await`` (session.rollback) would + # raise, ``except Exception`` wouldn't catch it (CancelledError is a + # BaseException), and the rest of cleanup — including session.close() + # — would never run. + with anyio.CancelScope(shield=True): + # Authoritative fallback cleanup for lock/cancel state. Middleware + # teardown can be skipped on some client-abort paths. + end_turn(str(chat_id)) + + if premium_reservation is not None and user_id: + await release_premium( + reservation=premium_reservation, user_id=user_id + ) + + await close_session_and_clear_ai_responding(session, chat_id) + + await finalize_assistant_message( + stream_result=stream_result, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + accumulator=accumulator, + log_prefix="stream_new_chat", + ) + + # Persist any sandbox-produced files to local storage so they remain + # downloadable after the Daytona sandbox auto-deletes. + if stream_result and stream_result.sandbox_files: + with contextlib.suppress(Exception): + from app.agents.new_chat.sandbox import ( + is_sandbox_enabled, + persist_and_delete_sandbox, + ) + + if is_sandbox_enabled(): + with anyio.CancelScope(shield=True): + await persist_and_delete_sandbox( + chat_id, stream_result.sandbox_files + ) + + # ``aafter_agent`` doesn't fire on ``interrupt()`` or early bailout. + # Skip on ``BusyError`` (caller never acquired the lock). + if not busy_error_raised: + with contextlib.suppress(Exception): + end_turn(str(chat_id)) + _perf_log.info( + "[stream_new_chat] end_turn cleanup (chat_id=%s)", chat_id + ) + + # Break circular refs held by the agent graph, tools, and LLM + # wrappers so the GC can reclaim them in a single pass. + agent = llm = connector_service = None # noqa: F841 + input_state = stream_result = None # noqa: F841 + session = None # noqa: F841 + + run_gc_pass(log_prefix="stream_new_chat", chat_id=chat_id) + close_chat_request_span( + span_cm=chat_span_cm, + span=chat_span, + chat_outcome=chat_outcome, + chat_agent_mode=chat_agent_mode, + flow=flow, + chat_error_category=chat_error_category, + duration_seconds=time.perf_counter() - _t_total, + ) From 885d4acda921ff8b8c0cb10171cf63bcffbc5845 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:50:03 +0200 Subject: [PATCH 013/133] refactor(chat): add streaming/flows/resume_chat/ per-concern leaf modules Three focused modules used by the upcoming resume-chat orchestrator: * runtime_context: build_resume_chat_runtime_context assembles the SurfSenseContextSchema for a resume turn (handles empty mention lists, since resume requests do not carry fresh @-mentions). * assistant_shell: persist_resume_assistant_shell writes a fresh assistant row for the resumed turn so the post-stream finalize has a target. * resume_routing: build_resume_routing collects the pending interrupts across paused subagents and slices the flat list of ResumeDecision[] into the correct (thread, subagent) buckets so LangGraph routes each decision back to the right paused tool call. Add-only; no orchestrator yet (next commit). --- .../flows/resume_chat/assistant_shell.py | 31 +++++++++ .../flows/resume_chat/resume_routing.py | 65 +++++++++++++++++++ .../flows/resume_chat/runtime_context.py | 23 +++++++ 3 files changed, 119 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py new file mode 100644 index 000000000..2f34387f8 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py @@ -0,0 +1,31 @@ +"""Pre-write a fresh assistant row for this resume turn. + +The original (interrupted) ``stream_new_chat`` invocation already persisted +its own assistant row anchored to a different ``turn_id``; resume allocates a +new ``turn_id`` (per-request, see ``orchestrator``) so we need a separate row +keyed on the same ``(thread_id, turn_id, ASSISTANT)`` invariant. + +Idempotent against migration 141's partial unique index — recovers the +existing id on retry. + +Resume does NOT emit ``data-user-message-id``: the user row is from the +original interrupted turn (different ``turn_id``) and is never re-persisted +here. See B5 in the ``sse-based_message_id_handshake`` plan. +""" + +from __future__ import annotations + +from app.tasks.chat.persistence import persist_assistant_shell + + +async def persist_resume_assistant_shell( + *, + chat_id: int, + user_id: str | None, + turn_id: str, +) -> int | None: + return await persist_assistant_shell( + chat_id=chat_id, + user_id=user_id, + turn_id=turn_id, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py new file mode 100644 index 000000000..300fbc9bd --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py @@ -0,0 +1,65 @@ +"""Route a flat ``decisions`` list back to the right paused subagent. + +Each pending interrupt is stamped with its originating ``tool_call_id`` (see +``checkpointed_subagent_middleware.propagation``) so the resume slicer can +re-target each ``HumanReview`` decision at the right ``tool_call_id``. + +LangGraph rejects scalar ``Command(resume=...)`` when multiple interrupts are +pending (parallel HITL); the mapped form works for the single-pause case too, +so we always use it. +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Any + +from app.utils.perf import get_perf_logger + +_perf_log = get_perf_logger() +logger = logging.getLogger(__name__) + + +@dataclass +class ResumeRoutingPayload: + """Resolved per-``tool_call_id`` resume slices + the lg-shaped resume map.""" + + routed_resume_value: dict[str, Any] + lg_resume_map: dict[str, Any] + + +async def build_resume_routing( + agent: Any, + *, + chat_id: int, + decisions: list[dict], +) -> ResumeRoutingPayload: + """Read parent_state, collect pending tool-calls, slice decisions, build map. + + The middleware reads its per-``tool_call_id`` resume slice from the + ``surfsense_resume_value`` configurable; parallel siblings each pop their + own entry so they never race. + """ + from app.agents.multi_agent_chat.middleware.main_agent.checkpointed_subagent_middleware.resume_routing import ( + build_lg_resume_map, + collect_pending_tool_calls, + slice_decisions_by_tool_call, + ) + + parent_state = await agent.aget_state( + {"configurable": {"thread_id": str(chat_id)}} + ) + pending = collect_pending_tool_calls(parent_state) + _perf_log.info( + "[hitl_route] resume_entry chat_id=%s decisions=%d pending_subagents=%d", + chat_id, + len(decisions), + len(pending), + ) + routed_resume_value = slice_decisions_by_tool_call(decisions, pending) + lg_resume_map = build_lg_resume_map(parent_state, routed_resume_value) + return ResumeRoutingPayload( + routed_resume_value=routed_resume_value, + lg_resume_map=lg_resume_map, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py new file mode 100644 index 000000000..59d5d8ca7 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py @@ -0,0 +1,23 @@ +"""Build the per-invocation ``SurfSenseContextSchema`` for a resume turn. + +Resume doesn't carry new ``mentioned_document_ids`` (those are seeded by the +original turn). We still build the context so future middleware extensions +can rely on ``runtime.context`` always being populated. +""" + +from __future__ import annotations + +from app.agents.new_chat.context import SurfSenseContextSchema + + +def build_resume_chat_runtime_context( + *, + search_space_id: int, + request_id: str | None, + turn_id: str, +) -> SurfSenseContextSchema: + return SurfSenseContextSchema( + search_space_id=search_space_id, + request_id=request_id, + turn_id=turn_id, + ) From cf0085575ca0275f134dbc04f30896b33fa5a50a Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:50:09 +0200 Subject: [PATCH 014/133] refactor(chat): add streaming/flows/resume_chat/orchestrator + flows public API Slim composition root for the resume-chat streaming flow. Mirrors the new_chat orchestrator but specialized for resumed turns: * no fresh user turn, no title generation, no image-capability gate * persists a fresh assistant shell for the resumed turn * applies build_resume_routing to dispatch user decisions to the correct paused subagent before invoking the agent * shares the same stream_loop + flow-local _recover closure for in- stream provider rate-limit recovery Also lands flows/__init__.py, which becomes the public chat-flow API: from app.tasks.chat.streaming.flows import stream_new_chat, stream_resume_chat Existing wiring (routes, contract test) still imports from the legacy app.tasks.chat.stream_new_chat module. Cutover is the next phase. --- .../tasks/chat/streaming/flows/__init__.py | 17 + .../streaming/flows/resume_chat/__init__.py | 12 + .../flows/resume_chat/orchestrator.py | 629 ++++++++++++++++++ 3 files changed, 658 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/__init__.py new file mode 100644 index 000000000..522db2fad --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/__init__.py @@ -0,0 +1,17 @@ +"""Top-level streaming flows: ``new_chat`` and ``resume_chat`` orchestrators. + +Re-exports the public entry points so callers can write:: + + from app.tasks.chat.streaming.flows import stream_new_chat, stream_resume_chat + +The orchestrators themselves live under ``new_chat/orchestrator.py`` and +``resume_chat/orchestrator.py`` (slim composition of the per-concern modules in +each flow folder and the building blocks in ``shared/``). +""" + +from __future__ import annotations + +from app.tasks.chat.streaming.flows.new_chat import stream_new_chat +from app.tasks.chat.streaming.flows.resume_chat import stream_resume_chat + +__all__ = ["stream_new_chat", "stream_resume_chat"] diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py new file mode 100644 index 000000000..ed0683e19 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py @@ -0,0 +1,12 @@ +"""Resume-chat streaming flow. + +Public entry point ``stream_resume_chat`` is the slim coroutine in +``orchestrator.py`` that composes the per-concern modules in this folder and +the building blocks under ``flows/shared/``. +""" + +from __future__ import annotations + +from app.tasks.chat.streaming.flows.resume_chat.orchestrator import stream_resume_chat + +__all__ = ["stream_resume_chat"] diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py new file mode 100644 index 000000000..b67ac987e --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py @@ -0,0 +1,629 @@ +"""``stream_resume_chat`` — public entry point for a HITL resume turn. + +Slim composition layer over the per-concern modules in this folder and the +building blocks under ``flows/shared/``. Mirrors ``stream_new_chat`` but: + + * No user-message persistence (the original turn already wrote it). + * No mentions / surfsense-doc / report context assembly (seeded by original). + * No title generation (only fires on first-response). + * Synchronous ``persist_assistant_shell`` call (we have no other in-flight + pre-stream work to overlap it with). + * ``input_data`` is a ``Command(resume=lg_resume_map)`` instead of a + LangChain message list. +""" + +from __future__ import annotations + +import contextlib +import gc +import logging +import sys +import time +import uuid as _uuid +from collections.abc import AsyncGenerator +from functools import partial +from typing import Any +from uuid import UUID + +import anyio + +from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent +from app.agents.new_chat.filesystem_selection import FilesystemMode, FilesystemSelection +from app.agents.new_chat.middleware.busy_mutex import end_turn +from app.config import config as _app_config +from app.db import ChatVisibility, async_session_maker, shielded_async_session +from app.observability import otel as ot +from app.services.chat_session_state_service import set_ai_responding +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.content_builder import AssistantContentBuilder +from app.tasks.chat.streaming.agent.builder import build_main_agent_for_thread +from app.tasks.chat.streaming.contract.file_contract import log_file_contract +from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error +from app.tasks.chat.streaming.flows.resume_chat.assistant_shell import ( + persist_resume_assistant_shell, +) +from app.tasks.chat.streaming.flows.resume_chat.resume_routing import ( + build_resume_routing, +) +from app.tasks.chat.streaming.flows.resume_chat.runtime_context import ( + build_resume_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.shared.assistant_finalize import ( + finalize_assistant_message, +) +from app.tasks.chat.streaming.flows.shared.finalize_emit import iter_token_usage_frame +from app.tasks.chat.streaming.flows.shared.finally_cleanup import ( + close_session_and_clear_ai_responding, + run_gc_pass, +) +from app.tasks.chat.streaming.flows.shared.first_frames import ( + iter_final_frames, + iter_initial_frames, +) +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( + get_chat_checkpointer, + setup_connector_and_firecrawl, +) +from app.tasks.chat.streaming.flows.shared.premium_quota import ( + PremiumReservation, + finalize_premium, + needs_premium_quota, + release_premium, + reserve_premium, +) +from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import ( + can_recover_provider_rate_limit, + log_rate_limit_recovered, + reroute_to_next_auto_pin, +) +from app.tasks.chat.streaming.flows.shared.span import ( + close_chat_request_span, + open_chat_request_span, + set_agent_mode, +) +from app.tasks.chat.streaming.flows.shared.stream_loop import run_stream_loop +from app.tasks.chat.streaming.flows.shared.terminal_error import ( + handle_terminal_exception, +) +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.tasks.chat.streaming.shared.utils import resume_step_prefix +from app.utils.perf import get_perf_logger, log_system_snapshot + +logger = logging.getLogger(__name__) +_perf_log = get_perf_logger() + + +async def stream_resume_chat( + chat_id: int, + search_space_id: int, + decisions: list[dict], + user_id: str | None = None, + llm_config_id: int = -1, + thread_visibility: ChatVisibility | None = None, + filesystem_selection: FilesystemSelection | None = None, + request_id: str | None = None, + disabled_tools: list[str] | None = None, +) -> AsyncGenerator[str, None]: + """Resume a paused HITL turn with the user's decisions. + + Mirrors ``stream_new_chat`` except for the resume-specific routing of + ``decisions`` to per-``tool_call_id`` slices (``build_resume_routing``). + """ + streaming_service = VercelStreamingService() + stream_result = StreamResult() + _t_total = time.perf_counter() + fs_mode = filesystem_selection.mode.value if filesystem_selection else "cloud" + fs_platform = ( + filesystem_selection.client_platform.value if filesystem_selection else "web" + ) + stream_result.request_id = request_id + stream_result.turn_id = f"{chat_id}:{int(time.time() * 1000)}" + stream_result.filesystem_mode = fs_mode + stream_result.client_platform = fs_platform + + chat_agent_mode = "unknown" + chat_outcome = "success" + chat_error_category: str | None = None + chat_span_cm, chat_span = open_chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow="resume", + request_id=request_id, + turn_id=stream_result.turn_id, + filesystem_mode=fs_mode, + client_platform=fs_platform, + agent_mode=chat_agent_mode, + ) + log_file_contract("turn_start", stream_result) + _perf_log.info( + "[stream_resume] filesystem_mode=%s client_platform=%s", + fs_mode, + fs_platform, + ) + + from app.services.token_tracking_service import start_turn + + accumulator = start_turn() + + premium_reservation: PremiumReservation | None = None + busy_error_raised = False + + emit_stream_error = partial( + emit_stream_terminal_error, + streaming_service=streaming_service, + flow="resume", + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + ) + + session = async_session_maker() + try: + if user_id: + await set_ai_responding(session, chat_id, UUID(user_id)) + + requested_llm_config_id = llm_config_id + + # --- LLM config --- + + _t0 = time.perf_counter() + try: + from app.services.auto_model_pin_service import ( + resolve_or_get_pinned_llm_config_id, + ) + + pinned = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=llm_config_id, + ) + llm_config_id = pinned.resolved_llm_config_id + ot.add_event( + "model.pin.resolved", + { + "pin.requested_id": requested_llm_config_id, + "pin.resolved_id": llm_config_id, + "pin.requires_image_input": False, + }, + ) + except ValueError as pin_error: + yield emit_stream_error( + message=str(pin_error), + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + + llm, agent_config, llm_load_error = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + _perf_log.info( + "[stream_resume] LLM config loaded in %.3fs", time.perf_counter() - _t0 + ) + + if needs_premium_quota(agent_config, user_id): + premium_reservation = await reserve_premium( + agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + ) + if not premium_reservation.allowed: + ot.add_event( + "quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"} + ) + if requested_llm_config_id == 0: + try: + pinned_fb = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=0, + force_repin_free=True, + ) + llm_config_id = pinned_fb.resolved_llm_config_id + ot.add_event( + "model.repin", + { + "repin.reason": "premium_quota_exhausted", + "repin.to_config_id": llm_config_id, + }, + ) + except ValueError as pin_error: + yield emit_stream_error( + message=str(pin_error), + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + llm, agent_config, llm_load_error = await load_llm_bundle( + session, + config_id=llm_config_id, + search_space_id=search_space_id, + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + premium_reservation = None + from app.tasks.chat.streaming.errors.classifier import ( + log_chat_stream_error, + ) + + log_chat_stream_error( + flow="resume", + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=( + "Premium quota exhausted on pinned model; " + "auto-fallback switched to a free model" + ), + extra={ + "fallback_config_id": llm_config_id, + "auto_fallback": True, + }, + ) + else: + yield emit_stream_error( + message=( + "Buy more tokens to continue with this model, or " + "switch to a free model" + ), + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + extra={ + "resolved_config_id": llm_config_id, + "auto_fallback": False, + }, + ) + yield streaming_service.format_done() + return + + if not llm: + yield emit_stream_error( + message="Failed to create LLM instance", + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + + # --- Pre-stream setup --- + + _t0 = time.perf_counter() + connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( + session, search_space_id=search_space_id + ) + _perf_log.info( + "[stream_resume] Connector service + firecrawl key in %.3fs", + time.perf_counter() - _t0, + ) + + _t0 = time.perf_counter() + checkpointer = await get_chat_checkpointer() + _perf_log.info( + "[stream_resume] Checkpointer ready in %.3fs", time.perf_counter() - _t0 + ) + + visibility = thread_visibility or ChatVisibility.PRIVATE + use_multi_agent = bool(_app_config.MULTI_AGENT_CHAT_ENABLED) + chat_agent_mode = "multi" if use_multi_agent else "single" + set_agent_mode(chat_span, chat_agent_mode) + + _t0 = time.perf_counter() + agent_factory = ( + create_multi_agent_chat_deep_agent + if use_multi_agent + else create_surfsense_deep_agent + ) + agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + ) + _perf_log.info( + "[stream_resume] Agent created in %.3fs", time.perf_counter() - _t0 + ) + + # Release the transaction before streaming (same rationale as stream_new_chat). + await session.commit() + session.expunge_all() + + _perf_log.info( + "[stream_resume] Total pre-stream setup in %.3fs (chat_id=%s)", + time.perf_counter() - _t_total, + chat_id, + ) + + # --- Resume routing --- + + from langgraph.types import Command + + routing = await build_resume_routing( + agent, chat_id=chat_id, decisions=decisions + ) + + config = { + "configurable": { + "thread_id": str(chat_id), + "request_id": request_id or "unknown", + "turn_id": stream_result.turn_id, + # Per-``tool_call_id`` resume slices read by + # ``SurfSenseCheckpointedSubAgentMiddleware``. Parallel + # siblings each pop their own entry, so they never race. + "surfsense_resume_value": routing.routed_resume_value, + }, + # Same rationale as ``stream_new_chat``: effectively uncapped to + # mirror the agent default and OpenCode's session loop. Doom-loop + # / call-limit middleware enforce the real ceiling. + "recursion_limit": 10_000, + } + + # --- First SSE frames --- + + for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + yield sse + + # --- Assistant-shell persistence + id frame --- + + assistant_message_id = await persist_resume_assistant_shell( + chat_id=chat_id, + user_id=user_id, + turn_id=stream_result.turn_id, + ) + if assistant_message_id is None: + yield emit_stream_error( + message=( + "We couldn't initialize the assistant message. Please try again." + ), + error_kind="server_error", + error_code="MESSAGE_PERSIST_FAILED", + ) + for sse in iter_final_frames(streaming_service): + yield sse + return + + yield streaming_service.format_data( + "assistant-message-id", + {"message_id": assistant_message_id, "turn_id": stream_result.turn_id}, + ) + + stream_result.assistant_message_id = assistant_message_id + stream_result.content_builder = AssistantContentBuilder() + + runtime_context = build_resume_chat_runtime_context( + search_space_id=search_space_id, + request_id=request_id, + turn_id=stream_result.turn_id, + ) + + # --- Stream loop --- + + _t_stream_start = time.perf_counter() + runtime_rate_limit_recovered = False + + def _on_first_event() -> None: + _perf_log.info( + "[stream_resume] First agent event in %.3fs (stream), %.3fs (total) (chat_id=%s)", + time.perf_counter() - _t_stream_start, + time.perf_counter() - _t_total, + chat_id, + ) + + async def _recover(exc: BaseException, first_event_seen: bool): + nonlocal llm_config_id, llm, agent_config, runtime_rate_limit_recovered + if not can_recover_provider_rate_limit( + exc, + first_event_seen=first_event_seen, + runtime_rate_limit_recovered=runtime_rate_limit_recovered, + requested_llm_config_id=requested_llm_config_id, + current_llm_config_id=llm_config_id, + ): + return None + runtime_rate_limit_recovered = True + previous_config_id = llm_config_id + llm_config_id = await reroute_to_next_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + current_llm_config_id=llm_config_id, + requires_image_input=False, + ) + new_llm, new_agent_config, llm_load_err = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_err: + return None + llm = new_llm + agent_config = new_agent_config + + _t_rebuild = time.perf_counter() + new_agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + ) + _perf_log.info( + "[stream_resume] Runtime rate-limit recovery repinned " + "config_id=%s -> %s and rebuilt agent in %.3fs", + previous_config_id, + llm_config_id, + time.perf_counter() - _t_rebuild, + ) + log_rate_limit_recovered( + flow="resume", + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + previous_config_id=previous_config_id, + new_config_id=llm_config_id, + ) + return new_agent + + async for sse in run_stream_loop( + agent=agent, + streaming_service=streaming_service, + config=config, + input_data=Command(resume=routing.lg_resume_map), + stream_result=stream_result, + step_prefix=resume_step_prefix(stream_result.turn_id), + fallback_commit_search_space_id=search_space_id, + fallback_commit_created_by_id=user_id, + fallback_commit_filesystem_mode=( + filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + ), + fallback_commit_thread_id=chat_id, + runtime_context=runtime_context, + content_builder=stream_result.content_builder, + recover=_recover, + on_first_event=_on_first_event, + ): + yield sse + + _perf_log.info( + "[stream_resume] Agent stream completed in %.3fs (chat_id=%s)", + time.perf_counter() - _t_stream_start, + chat_id, + ) + + # --- Finalize --- + + if stream_result.is_interrupted: + ot.add_event("chat.interrupted", {"chat.flow": "resume"}) + for sse in iter_token_usage_frame( + streaming_service, + accumulator=accumulator, + log_label="interrupted resume_chat", + ): + yield sse + yield streaming_service.format_finish_step() + yield streaming_service.format_finish() + yield streaming_service.format_done() + return + + if premium_reservation is not None and user_id: + await finalize_premium( + reservation=premium_reservation, + user_id=user_id, + accumulator=accumulator, + ) + premium_reservation = None + + for sse in iter_token_usage_frame( + streaming_service, accumulator=accumulator, log_label="normal resume_chat" + ): + yield sse + + for sse in iter_final_frames(streaming_service): + yield sse + + except Exception as exc: + frames, summary = handle_terminal_exception( + exc, + flow="resume", + flow_label="resume", + log_prefix="stream_resume_chat", + streaming_service=streaming_service, + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + chat_span=chat_span, + ) + if summary["busy_error_raised"]: + busy_error_raised = True + chat_outcome = summary["chat_outcome"] + chat_error_category = summary["chat_error_category"] + for sse in frames: + yield sse + + finally: + with anyio.CancelScope(shield=True): + end_turn(str(chat_id)) + + if premium_reservation is not None and user_id: + await release_premium( + reservation=premium_reservation, user_id=user_id + ) + + await close_session_and_clear_ai_responding(session, chat_id) + + await finalize_assistant_message( + stream_result=stream_result, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + accumulator=accumulator, + log_prefix="stream_resume", + ) + + # Release the lock from the original interrupted turn or any + # re-interrupt/bailout. Skip on ``BusyError`` (lock not held here). + if not busy_error_raised: + with contextlib.suppress(Exception): + end_turn(str(chat_id)) + _perf_log.info( + "[stream_resume] end_turn cleanup (chat_id=%s)", chat_id + ) + + agent = llm = connector_service = None # noqa: F841 + stream_result = None # noqa: F841 + session = None # noqa: F841 + + run_gc_pass(log_prefix="stream_resume", chat_id=chat_id) + close_chat_request_span( + span_cm=chat_span_cm, + span=chat_span, + chat_outcome=chat_outcome, + chat_agent_mode=chat_agent_mode, + flow="resume", + chat_error_category=chat_error_category, + duration_seconds=time.perf_counter() - _t_total, + ) From cfdad85058083dd822655611ae1390e4873d587a Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:50:18 +0200 Subject: [PATCH 015/133] test(chat): add parity tests for streaming/flows/ parallel refactor Adds 34 tests under tests/unit/tasks/chat/streaming/ that cover the new flows tree against the legacy stream_new_chat.py module to gate the upcoming cutover. Coverage: * Public entry points: stream_new_chat and stream_resume_chat are async generator functions whose parameter signatures (name, kind, annotation, default) match the legacy versions one-for-one. Uses a normalized-annotation comparison so PEP-563 vs eager-annotation representation differences are tolerated. * Extracted helpers: image-capability gate, runtime-context builders for new-chat and resume-chat, LLM-bundle dispatcher, premium-quota needs check + reservation dataclass, rate-limit recovery truth table, persistence-spawn registration/self-unregistration, await helpers. * SSE frame iterators: iter_initial_frames + iter_final_frames emit the canonical sequence; iter_token_usage_frame skips on None. * Initial thinking step: 4 parametrized branches (text, image-only, empty, mentioned-docs), long-query truncation, many-docs collapse. These tests are scaffolding for the cutover and will be removed once the legacy module is deleted. --- .../test_parallel_refactor_parity.py | 582 ++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py new file mode 100644 index 000000000..eb24b4df8 --- /dev/null +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py @@ -0,0 +1,582 @@ +"""Parity gate for the parallel refactor of ``stream_new_chat.py``. + +The new tree under ``app.tasks.chat.streaming.flows`` is built side-by-side with +the legacy monolithic ``app.tasks.chat.stream_new_chat`` so we can cut over +atomically. This file pins externally-observable behaviour at module +boundaries so a divergence between the two trees fails loudly *before* the +cutover. + +What we verify: + + 1. **Signature parity** — ``stream_new_chat`` / ``stream_resume_chat`` from + the new tree have the same call signature as the originals. + 2. **Helper extraction parity** — the SRP modules in ``flows/`` produce the + same outputs as the inline code in the legacy file for representative + inputs (initial thinking step, image-capability gate, runtime context, + SSE frame sequences, token-usage frame shape, persistence guards). + 3. **Wrapper delegation** — wrappers like ``load_llm_bundle`` / + ``can_recover_provider_rate_limit`` exist and are addressable. + +Delete this file along with ``stream_new_chat.py`` once the cutover is done +(see the parent refactor plan). +""" + +from __future__ import annotations + +import asyncio +import inspect +from dataclasses import dataclass +from typing import Any +from unittest.mock import AsyncMock, patch + +import pytest + +from app.agents.new_chat.context import SurfSenseContextSchema +from app.services.new_streaming_service import VercelStreamingService + +from app.tasks.chat.stream_new_chat import ( + stream_new_chat as old_stream_new_chat, + stream_resume_chat as old_stream_resume_chat, +) +from app.tasks.chat.streaming.flows import ( + stream_new_chat as new_stream_new_chat, + stream_resume_chat as new_stream_resume_chat, +) +from app.tasks.chat.streaming.flows.new_chat.initial_thinking_step import ( + build_initial_thinking_step, +) +from app.tasks.chat.streaming.flows.new_chat.llm_capability import ( + check_image_input_capability, +) +from app.tasks.chat.streaming.flows.new_chat.persistence_spawn import ( + await_persist_task, + spawn_persist_assistant_shell_task, + spawn_persist_user_task, + spawn_set_ai_responding_bg, +) +from app.tasks.chat.streaming.flows.new_chat.runtime_context import ( + build_new_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.resume_chat.runtime_context import ( + build_resume_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.shared.finalize_emit import iter_token_usage_frame +from app.tasks.chat.streaming.flows.shared.first_frames import ( + iter_final_frames, + iter_initial_frames, +) +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.premium_quota import ( + PremiumReservation, + needs_premium_quota, +) +from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import ( + can_recover_provider_rate_limit, +) + +pytestmark = pytest.mark.unit + + +# --------------------------------------------------------------------- signature + + +def _normalize_annotation(ann: Any) -> str: + """Compare-friendly form for an annotation. + + The legacy ``stream_new_chat.py`` does NOT use ``from __future__ import + annotations``, so its annotations are evaluated at import time and come + back as type objects / typing generics. The new tree DOES use it, so its + annotations are PEP-563 strings. + + Both reprs describe the same types — strip the module prefixes / typing + namespace + the ```` wrapper so we compare the canonical + declared form. + """ + if ann is inspect.Signature.empty: + return "" + raw = ann if isinstance(ann, str) else repr(ann) + cleaned = ( + raw.replace("typing.", "") + .replace("collections.abc.", "") + .replace("app.db.", "") + .replace("app.agents.new_chat.filesystem_selection.", "") + .replace("app.agents.new_chat.context.", "") + ) + # Unwrap ```` → ``int`` (legacy-side type objects). + if cleaned.startswith(""): + cleaned = cleaned[len("")] + return cleaned + + +def _normalize_sig(sig: inspect.Signature) -> list[tuple[str, Any, str]]: + return [ + (p.name, p.default, _normalize_annotation(p.annotation)) + for p in sig.parameters.values() + ] + + +def test_stream_new_chat_signature_matches_legacy() -> None: + old = inspect.signature(old_stream_new_chat) + new = inspect.signature(new_stream_new_chat) + assert _normalize_sig(new) == _normalize_sig(old) + assert _normalize_annotation(new.return_annotation) == _normalize_annotation( + old.return_annotation + ) + + +def test_stream_resume_chat_signature_matches_legacy() -> None: + old = inspect.signature(old_stream_resume_chat) + new = inspect.signature(new_stream_resume_chat) + assert _normalize_sig(new) == _normalize_sig(old) + assert _normalize_annotation(new.return_annotation) == _normalize_annotation( + old.return_annotation + ) + + +def test_orchestrators_are_async_generator_functions() -> None: + assert inspect.isasyncgenfunction(new_stream_new_chat) + assert inspect.isasyncgenfunction(new_stream_resume_chat) + + +# ------------------------------------------------------------ initial thinking + + +@dataclass +class _FakeSurfsenseDoc: + """Stand-in for ``SurfsenseDocsDocument`` with just the field we read.""" + + title: str + + +@pytest.mark.parametrize( + "user_query, image_urls, docs, expected_title, expected_action", + [ + ("hello world", None, [], "Understanding your request", "Processing"), + ("", ["data:image/png;base64,AAA"], [], "Understanding your request", "Processing"), + ("", None, [], "Understanding your request", "Processing"), + ( + "doc question", + None, + [_FakeSurfsenseDoc(title="My Doc")], + "Analyzing referenced content", + "Analyzing", + ), + ], +) +def test_initial_thinking_step_branches( + user_query: str, + image_urls: list[str] | None, + docs: list[Any], + expected_title: str, + expected_action: str, +) -> None: + step = build_initial_thinking_step( + user_query=user_query, + user_image_data_urls=image_urls, + mentioned_surfsense_docs=docs, # type: ignore[arg-type] + ) + assert step.step_id == "thinking-1" + assert step.title == expected_title + assert len(step.items) == 1 + assert step.items[0].startswith(f"{expected_action}: ") + + +def test_initial_thinking_step_truncates_long_query() -> None: + long_query = "x" * 200 + step = build_initial_thinking_step( + user_query=long_query, + user_image_data_urls=None, + mentioned_surfsense_docs=[], + ) + # 80-char truncation + ellipsis, sandwiched after "Processing: ". + assert "..." in step.items[0] + item = step.items[0] + payload = item[len("Processing: ") :] + assert payload.startswith("x" * 80) and payload.endswith("...") + + +def test_initial_thinking_step_collapses_many_doc_names() -> None: + docs = [_FakeSurfsenseDoc(title=f"Doc {i}") for i in range(5)] + step = build_initial_thinking_step( + user_query="q", + user_image_data_urls=None, + mentioned_surfsense_docs=docs, # type: ignore[arg-type] + ) + assert "[5 docs]" in step.items[0] + + +# ------------------------------------------------------------ capability gate + + +def test_image_capability_passes_without_images() -> None: + assert check_image_input_capability( + user_image_data_urls=None, agent_config=None + ) is None + + +def test_image_capability_passes_when_capability_unknown() -> None: + """Unknown / unmapped models are not blocked — only models LiteLLM has + *explicitly* marked text-only trip the gate.""" + + class _AgentConfig: + provider = "openrouter" + model_name = "unknown-mystery-model" + custom_provider = None + config_name = "Unknown" + litellm_params: dict[str, Any] = {} + + with patch( + "app.services.provider_capabilities.is_known_text_only_chat_model", + return_value=False, + ): + assert ( + check_image_input_capability( + user_image_data_urls=["data:image/png;base64,AAA"], + agent_config=_AgentConfig(), # type: ignore[arg-type] + ) + is None + ) + + +def test_image_capability_blocks_known_text_only_models() -> None: + class _AgentConfig: + provider = "openai" + model_name = "gpt-3.5-turbo" + custom_provider = None + config_name = "GPT-3.5" + litellm_params: dict[str, Any] = {"base_model": "gpt-3.5-turbo"} + + with patch( + "app.services.provider_capabilities.is_known_text_only_chat_model", + return_value=True, + ): + result = check_image_input_capability( + user_image_data_urls=["data:image/png;base64,AAA"], + agent_config=_AgentConfig(), # type: ignore[arg-type] + ) + assert result is not None + message, error_code = result + assert error_code == "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" + assert "GPT-3.5" in message + + +# ---------------------------------------------------------------- runtime ctx + + +def test_new_chat_runtime_context_prefers_accepted_folder_ids() -> None: + ctx = build_new_chat_runtime_context( + search_space_id=7, + mentioned_document_ids=[1, 2], + accepted_folder_ids=[10], + mentioned_folder_ids=[20, 30], + request_id="req", + turn_id="t1", + ) + assert isinstance(ctx, SurfSenseContextSchema) + assert ctx.search_space_id == 7 + assert list(ctx.mentioned_document_ids) == [1, 2] + assert list(ctx.mentioned_folder_ids) == [10] + assert ctx.request_id == "req" + assert ctx.turn_id == "t1" + + +def test_new_chat_runtime_context_falls_back_to_mentioned_folder_ids() -> None: + ctx = build_new_chat_runtime_context( + search_space_id=7, + mentioned_document_ids=None, + accepted_folder_ids=[], + mentioned_folder_ids=[20, 30], + request_id=None, + turn_id="t2", + ) + assert list(ctx.mentioned_folder_ids) == [20, 30] + + +def test_resume_chat_runtime_context_empty_mention_lists() -> None: + ctx = build_resume_chat_runtime_context( + search_space_id=42, request_id="req-r", turn_id="t-r" + ) + assert ctx.search_space_id == 42 + assert ctx.request_id == "req-r" + assert ctx.turn_id == "t-r" + + +# ---------------------------------------------------------------- SSE frames + + +def test_iter_initial_frames_emits_canonical_sequence() -> None: + svc = VercelStreamingService() + frames = list(iter_initial_frames(svc, turn_id="42:1700000000000")) + # Exactly 4 frames: message_start, start_step, turn-info (turn_id), turn-status (busy). + assert len(frames) == 4 + assert "42:1700000000000" in frames[2] + assert '"status":"busy"' in frames[3] or '"status": "busy"' in frames[3] + + +def test_iter_final_frames_emits_idle_then_finish_done() -> None: + svc = VercelStreamingService() + frames = list(iter_final_frames(svc)) + assert len(frames) == 4 + assert '"status":"idle"' in frames[0] or '"status": "idle"' in frames[0] + + +# ----------------------------------------------------------- token usage frame + + +class _FakeAccumulator: + """Minimal stand-in covering only the fields ``iter_token_usage_frame`` reads.""" + + def __init__(self, summary: Any = None) -> None: + self._summary = summary + self.calls = [1, 2, 3] + self.grand_total = 100 + self.total_cost_micros = 50_000 + self.total_prompt_tokens = 60 + self.total_completion_tokens = 40 + + def per_message_summary(self) -> Any: + return self._summary + + def serialized_calls(self) -> list[Any]: + return list(self.calls) + + +def test_token_usage_frame_skipped_when_no_summary() -> None: + svc = VercelStreamingService() + frames = list( + iter_token_usage_frame( + svc, + accumulator=_FakeAccumulator(summary=None), # type: ignore[arg-type] + log_label="parity-empty", + ) + ) + assert frames == [] + + +def test_token_usage_frame_emitted_when_summary_present() -> None: + svc = VercelStreamingService() + frames = list( + iter_token_usage_frame( + svc, + accumulator=_FakeAccumulator(summary=[{"m": "x", "t": 100}]), # type: ignore[arg-type] + log_label="parity-populated", + ) + ) + assert len(frames) == 1 + # Field shape on the wire is fixed by the FE; assert each surfaces. + payload = frames[0] + for key in ( + '"prompt_tokens":60', + '"completion_tokens":40', + '"total_tokens":100', + '"cost_micros":50000', + ): + assert key in payload.replace(" ", "") + + +# ------------------------------------------------------------------ llm_bundle + + +def test_load_llm_bundle_routes_negative_id_to_yaml_loader() -> None: + async def _run() -> tuple[Any, Any, str | None]: + with ( + patch( + "app.tasks.chat.streaming.flows.shared.llm_bundle.load_global_llm_config_by_id", + return_value=None, + ), + ): + return await load_llm_bundle( + session=AsyncMock(), # type: ignore[arg-type] + config_id=-1, + search_space_id=7, + ) + + llm, agent_config, error = asyncio.run(_run()) + assert llm is None + assert agent_config is None + assert error is not None and "id -1" in error + + +def test_load_llm_bundle_routes_nonnegative_id_to_db_loader() -> None: + async def _run() -> tuple[Any, Any, str | None]: + with ( + patch( + "app.tasks.chat.streaming.flows.shared.llm_bundle.load_agent_config", + new=AsyncMock(return_value=None), + ), + ): + return await load_llm_bundle( + session=AsyncMock(), # type: ignore[arg-type] + config_id=12, + search_space_id=7, + ) + + llm, agent_config, error = asyncio.run(_run()) + assert llm is None + assert agent_config is None + assert error is not None and "id 12" in error + + +# ----------------------------------------------------------------- premium quota + + +def test_needs_premium_quota_requires_user_and_premium_flag() -> None: + class _AgentConfig: + is_premium = True + + class _NonPremium: + is_premium = False + + assert needs_premium_quota(_AgentConfig(), "user-1") is True # type: ignore[arg-type] + assert needs_premium_quota(_AgentConfig(), None) is False # type: ignore[arg-type] + assert needs_premium_quota(_NonPremium(), "user-1") is False # type: ignore[arg-type] + assert needs_premium_quota(None, "user-1") is False + + +def test_premium_reservation_dataclass_shape() -> None: + # Sanity: the dataclass exists and carries the fields the orchestrator uses. + r = PremiumReservation(request_id="abc", reserved_micros=100, allowed=True) + assert r.request_id == "abc" + assert r.reserved_micros == 100 + assert r.allowed is True + + +# ----------------------------------------------------------- rate-limit guard + + +@pytest.mark.parametrize( + "first_event_seen, recovered, requested_id, current_id, expected", + [ + (False, False, 0, -1, True), + # Already recovered: no second pass. + (False, True, 0, -1, False), + # User explicitly picked a config: don't silently switch. + (False, False, 5, -1, False), + # Already on a database-backed (positive) id. + (False, False, 0, 7, False), + # User has already seen output: silent rebuild not possible. + (True, False, 0, -1, False), + ], +) +def test_can_recover_provider_rate_limit_truth_table( + first_event_seen: bool, + recovered: bool, + requested_id: int, + current_id: int, + expected: bool, +) -> None: + # Use a known rate-limit-shaped exception so the helper's last condition + # is satisfied; the guard only short-circuits to False when one of the + # *other* preconditions fails. + exc = Exception('{"error":{"type":"rate_limit_error","message":"slow"}}') + assert ( + can_recover_provider_rate_limit( + exc, + first_event_seen=first_event_seen, + runtime_rate_limit_recovered=recovered, + requested_llm_config_id=requested_id, + current_llm_config_id=current_id, + ) + is expected + ) + + +def test_can_recover_provider_rate_limit_rejects_non_rate_limit_exception() -> None: + assert ( + can_recover_provider_rate_limit( + ValueError("not a rate limit"), + first_event_seen=False, + runtime_rate_limit_recovered=False, + requested_llm_config_id=0, + current_llm_config_id=-1, + ) + is False + ) + + +# --------------------------------------------------------- persistence spawn + + +def test_spawn_set_ai_responding_bg_noop_without_user_id() -> None: + async def _run() -> set[asyncio.Task]: + background: set[asyncio.Task] = set() + spawn_set_ai_responding_bg( + chat_id=1, user_id=None, background_tasks=background + ) + return background + + bg = asyncio.run(_run()) + assert bg == set() + + +def test_spawn_persist_user_task_registers_and_self_unregisters() -> None: + async def _run() -> tuple[int, int]: + background: set[asyncio.Task] = set() + with patch( + "app.tasks.chat.streaming.flows.new_chat.persistence_spawn.persist_user_turn", + new=AsyncMock(return_value=99), + ): + task = spawn_persist_user_task( + chat_id=1, + user_id="u", + turn_id="t", + user_query="hi", + user_image_data_urls=None, + mentioned_documents=None, + background_tasks=background, + ) + size_before_await = len(background) + result = await asyncio.shield(task) + # Give the done-callback one event-loop tick to run. + await asyncio.sleep(0) + return size_before_await, result # type: ignore[return-value] + + size_before, result = asyncio.run(_run()) + assert size_before == 1 + assert result == 99 + + +def test_spawn_persist_assistant_shell_task_registers() -> None: + async def _run() -> int | None: + background: set[asyncio.Task] = set() + with patch( + "app.tasks.chat.streaming.flows.new_chat.persistence_spawn.persist_assistant_shell", + new=AsyncMock(return_value=42), + ): + task = spawn_persist_assistant_shell_task( + chat_id=1, + user_id="u", + turn_id="t", + background_tasks=background, + ) + return await asyncio.shield(task) + + assert asyncio.run(_run()) == 42 + + +def test_await_persist_task_returns_none_on_failure() -> None: + async def _run() -> int | None: + async def _boom() -> int: + raise RuntimeError("DB down") + + task = asyncio.create_task(_boom()) + return await await_persist_task( + task, + chat_id=1, + turn_id="t", + log_label="parity-failure", + ) + + assert asyncio.run(_run()) is None + + +def test_await_persist_task_returns_none_for_none_input() -> None: + async def _run() -> int | None: + return await await_persist_task( + None, + chat_id=1, + turn_id="t", + log_label="parity-none", + ) + + assert asyncio.run(_run()) is None From 123f0d3b5d7e5b56c1fccd9583ae4699e7e6bc62 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:30:21 +0200 Subject: [PATCH 016/133] docs(automation): add v2 design plan baseline Track the initial v2 design document for the SurfSense automation feature. This is the baseline snapshot of the design before applying the v1-minimum scope narrowing (capability trimming, MCP deferral, queue-routing deferral). Subsequent commits trim this down to the v1 scope. --- automation-design-plan.md | 1395 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1395 insertions(+) create mode 100644 automation-design-plan.md diff --git a/automation-design-plan.md b/automation-design-plan.md new file mode 100644 index 000000000..072f7ad99 --- /dev/null +++ b/automation-design-plan.md @@ -0,0 +1,1395 @@ +# SurfSense Automation Feature — Design Plan (v2) + +A generic, extensible automation system for SurfSense that lets users (and +future SurfSense features) trigger agent work on a schedule, on an external +event, or on demand — with the ability to author automations either by hand +or from a natural-language description that yields an editable, structured +definition. + +This document supersedes the v1 draft. It folds in the design audit pass and +the corrections from working through worked examples (notably: removing the +connector bias, clarifying the executor's role, integrating MCP cleanly, and +committing to JSON Schema as the single declarative language). + +--- + +## 1. The load-bearing principle + +> **The JSON definition is the program. Everything else is interpreter.** + +Every decision in this document serves that principle. If we ever face a +design choice and one option lets some behavior leak out of the definition +into the engine, we pick the other option. + +Three properties follow from this principle, and they're the reason the +system will survive feature growth: + +- **Reproducibility** — same definition + same inputs → same observable + behavior, regardless of which version of the engine runs it. +- **Portability** — definitions can be exported, imported, version- + controlled, code-reviewed, and shared across SurfSense instances. +- **LLM tractability** — the NL authoring flow works because the LLM only + needs to produce a self-contained JSON document that validates against a + schema. It doesn't need to understand the engine. + +--- + +## 2. The four-layer contract + +The system is structured as four layers. Layers 1, 2, and 4 are defined by +SurfSense developers (at registration time). Layer 3 is what users write +(or the NL generator produces). The runtime reads all four to do its job. + +| Layer | What it is | Defined by | +| ----- | ---------- | ---------- | +| **1. Capability registry** | What this SurfSense instance can do | Developers, at startup | +| **2. Action contract** | Per-action input/output schema | Developers, at startup | +| **3. Automation definition** | One concrete saved automation | Users (or NL generator) | +| **4. Trigger contract** | Per-trigger config and payload schemas | Developers, at startup | + +Each layer constrains the one above. The runtime reads all four but doesn't +know what's in them ahead of time. That's how a new capability or trigger +type becomes available across the engine without code changes outside its +registration. + +### Schema language + +Every shape in every layer is described in **JSON Schema (draft 2020-12).** +No exceptions, no parallel languages, no inline shorthand. Two documented +extensions on top: + +- `default: "$some_token"` — runtime-resolved defaults. The vocabulary is + fixed: `$last_fired_at`, `$creator`, `$space_default`. The engine resolves + these to values before validation. +- `x-surfsense-*` annotations — editor hints (widget type, autocomplete + source). The validator ignores them; the form editor reads them. + +--- + +## 3. Capability registry (Layer 1) + +A `Capability` is one discrete thing the SurfSense backend exposes — +"post a Slack message," "query the Search Space," "generate a podcast." It +is the atomic unit of "things automations can do." + +```python +@dataclass +class Capability: + id: str # "slack.post_message" + name: str # "Post Slack message" + description: str # for the NL generator + input_schema: dict # JSON Schema + output_schema: dict # JSON Schema + required_credentials: list[CredSpec] # what creds the handler needs + side_effects: set[SideEffect] # READ, WRITE, EXTERNAL_WRITE, + # COST_INCURRING, USER_VISIBLE + expected_duration_seconds: int # estimate or upper bound + cost_estimate: Callable[[dict], Decimal] # f(input) → estimated USD + handler: AsyncHandler +``` + +### Where capabilities live: a two-tier registry + +The capability registry has different storage requirements for different +kinds of capabilities. **Native capabilities and MCP capabilities have +different lifecycles**, so they're persisted differently: + +| Tier | What's there | Where it lives | Lifetime | +| --- | --- | --- | --- | +| **Native** | Capabilities defined in SurfSense's codebase (`search_space.query`, `agent.run`, etc.) | In-memory dict, populated at startup from `automations/capabilities/native.py` | Process lifetime, identical across all workers | +| **MCP (durable)** | The fact that this SearchSpace has connected to this MCP server, the tool list it exposes, credentials | PostgreSQL: `mcp_connections` and `mcp_tools` tables | Persistent across restarts and across time | +| **MCP (cached)** | Handler closures wrapping `(connection_id, tool_name)` | Per-worker in-memory cache, lazily built from the database on first reference | Process lifetime, rebuilt on demand | + +The reason this matters: **a user connects an MCP server on Monday, writes +an automation on Tuesday, the automation runs on Friday.** Between Monday +and Friday, workers will restart many times. Any state that only lives in +worker memory is gone. The closures generated at connection time would +not survive. + +So we split persistence by lifecycle: + +- Native capability handlers live in the codebase. Always available, no + need for the database. +- MCP capability metadata lives in the database, so the knowledge "this + SearchSpace has these capabilities" survives any restart. +- The actual closures are built on demand from the database state. They + live in worker memory only until the worker dies, at which point they + get rebuilt by the next worker that needs them. + +### MCP database schema + +```sql +CREATE TABLE mcp_connections ( + id UUID PRIMARY KEY, + search_space_id INT REFERENCES search_spaces(id), + server_url TEXT, + transport TEXT, -- "http", "stdio", etc. + name TEXT, -- "Slack (Acme workspace)" + access_token BYTEA, -- encrypted at rest + refresh_token BYTEA, -- encrypted at rest + expires_at TIMESTAMPTZ, + last_harvested_at TIMESTAMPTZ, + created_at TIMESTAMPTZ, + created_by INT REFERENCES users(id) +); + +CREATE TABLE mcp_tools ( + id UUID PRIMARY KEY, + connection_id UUID REFERENCES mcp_connections(id) ON DELETE CASCADE, + name TEXT, -- "post_message" + description TEXT, + input_schema JSONB, + output_schema JSONB, + side_effects TEXT[], -- inferred or admin-curated + UNIQUE (connection_id, name) +); +``` + +### MCP lifecycle: connect, harvest, invoke + +Three phases, each with distinct concerns. + +**Phase 1 — Connect (one-time, on user action).** User clicks "Connect +Slack MCP." OAuth flow completes. A row is added to `mcp_connections` +with the encrypted tokens. + +**Phase 2 — Harvest (right after connect, also re-runnable).** SurfSense +opens a temporary client to the MCP server, calls `tools/list`, and writes +one row to `mcp_tools` per discovered tool. The temporary client is then +discarded; only the database state persists. + +```python +async def harvest_mcp_server(connection_id: UUID, ctx): + connection = await ctx.db.get(MCPConnection, connection_id) + client = build_temporary_client(connection) + tools = await client.list_tools() + + # Replace existing tool rows for this connection + await ctx.db.execute( + delete(MCPTool).where(MCPTool.connection_id == connection_id) + ) + for tool in tools: + ctx.db.add(MCPTool( + connection_id=connection_id, + name=tool.name, + description=tool.description, + input_schema=tool.inputSchema, + output_schema=tool.outputSchema, + side_effects=infer_side_effects(tool), + )) + connection.last_harvested_at = now() + await ctx.db.commit() +``` + +Harvesting can be re-run on a schedule (say, daily) or on user request, +to pick up new tools the server has added. + +**Phase 3 — Invoke (every time a step references an MCP capability).** +This is where the closure gets built. The executor calls +`ctx.get_capability("slack.post_message")`. The worker's in-memory cache is +checked; on miss, the database is queried: + +```python +async def get_capability(capability_id: str, ctx: ActionContext) -> Capability: + cached = _WORKER_CAPABILITY_CACHE.get((ctx.search_space.id, capability_id)) + if cached: + return cached + + if is_native(capability_id): + capability = _NATIVE_REGISTRY[capability_id] + else: + # MCP path: look up tool metadata + tool_row = await ctx.db.execute( + select(MCPTool) + .join(MCPConnection) + .where(MCPConnection.search_space_id == ctx.search_space.id) + .where(tool_qualified_name(MCPTool, MCPConnection) == capability_id) + ) + capability = Capability( + id=capability_id, + input_schema=tool_row.input_schema, + output_schema=tool_row.output_schema, + side_effects=set(tool_row.side_effects), + handler=make_mcp_handler( + connection_id=tool_row.connection_id, + tool_name=tool_row.name, + ), + ) + + _WORKER_CAPABILITY_CACHE[(ctx.search_space.id, capability_id)] = capability + return capability +``` + +The closure created by `make_mcp_handler` captures only the connection ID +and tool name. When invoked, it asks `ctx.resolve_mcp_client(connection_id)` +to build an authenticated client from the connection record (including +token refresh if needed). That client is also transient — built per call, +discarded after. + +### Credentials: resolved at the moment of use + +The handler doesn't carry credentials and the closure doesn't capture them. +When invoked, the handler asks `ActionContext` for what it needs: + +```python +def make_mcp_handler(connection_id: UUID, tool_name: str): + async def handler(ctx: ActionContext, args: dict) -> Any: + # Credential resolution happens here, per call + client = await ctx.resolve_mcp_client(connection_id) + response = await client.call_tool(name=tool_name, arguments=args) + return response.content + return handler +``` + +`ctx.resolve_mcp_client(connection_id)`: +1. Loads the `mcp_connections` row +2. Decrypts the access token +3. Refreshes the token if it's expired (using the refresh token) +4. Constructs an `MCPClient` with the token set as a default authorization + header + +The HTTP library carries the auth header on every subsequent call the +client makes — the handler doesn't think about it after construction. + +For native capabilities calling external APIs directly, +`ctx.resolve_http_client(provider)` returns an authenticated `httpx` +client. For LLM operations, `ctx.resolve_llm(provider)` returns a +configured LLM client. **Three resolution methods, one pattern: the +context returns a client already authenticated.** + +Three properties this gives us: + +- **Credentials never appear in the automation definition.** The JSON + contains capability references and connection IDs, never tokens. +- **Credentials never appear in the LLM's context.** Even during + `agent_task`, the LLM sees tool descriptions only; the host holds + credentials and uses them when executing the tools the LLM requests. +- **Credentials are loaded per-call, not pre-loaded.** The credential + exists in memory only during the moment a handler is making a call. No + long-lived secrets in worker memory. + +--- + +## 4. Action contract (Layer 2) + +An `Action` is what a user references in a plan step. Most actions are +thin wrappers around one capability (e.g., `slack_post` wraps +`slack.post_message`). Some compose: `agent_task` is one action whose +handler invokes the LangGraph runtime, which in turn can call many +capabilities. + +```python +@dataclass +class ActionDefinition: + type: str # "agent_task", "slack_post" + name: str # for the UI + description: str # for the NL generator + config_schema: dict # JSON Schema for action.config + output_contract: dict | DynamicOutput # what it produces + uses_capabilities: list[str] # IDs from the registry + produces_artifacts: list[ArtifactSpec] # see §8 + handler: AsyncHandler +``` + +### Tight vs loose actions + +Two patterns coexist by design: + +- **Tight actions** (`slack_post`, `linear_create_issue`, `send_email`): + config_schema is fully specified, output_contract is fixed, handler is a + thin wrapper. ~20 LOC each. Used when the user knows exactly what they + want done — no LLM tokens spent on trivial work. + +- **Loose actions** (`agent_task`): config_schema accepts a `prompt` and a + `tools` allowlist; output_contract is *dynamic* — the user declares the + output shape they want via `output_schema` in the step config; the + handler asks the LLM to return that shape and validates. Used when + judgment is needed. + +The agent's tool list is **the same capabilities** that tight actions call +directly. One registry, two invocation modes. Adding a new MCP server gives +both modes access to its tools automatically. + +### How names in the definition become function calls + +The definition contains strings like `"action": "slack_post"`. The string is +just a name — it does not point to a function. At runtime, the executor +performs a **name-based lookup** against the action registry: + +```python +# step.action is a string from the JSON definition, e.g. "slack_post" +action_def = _ACTION_REGISTRY[step.action] # dict lookup +handler = action_def.handler # Python callable +result = await handler(ctx, resolved_config) # invocation +``` + +The registry is a Python dict (or a thin wrapper around one) populated at +process startup. Each entry in `automations/actions/*.py` calls a +`register_action(...)` function at module import time, putting its +`ActionDefinition` (including the handler function reference) into the +registry. + +The same pattern applies to capabilities. The definition references +capabilities by ID (`"slack.post_message"`); the capability registry maps +the ID to a `Capability` object holding the handler. Definitions never +reference Python code directly — they reference names that the registry +resolves to code. + +This separation is what makes the contract portable. The definition is +pure data. The registry is the engine's runtime vocabulary. They meet at +name-based lookup; nothing else crosses the boundary. + +### The full expressive spectrum + +The contract supports a continuous spectrum from purely deterministic to +fully agentic. Six practical shapes worth recognizing: + +| Shape | Example | Cost / latency profile | +| --- | --- | --- | +| **1. Direct call** | `slack_post` with literal channel and template | No LLM. ~200ms. Fractions of a cent. | +| **2. Direct call with computed inputs** | `linear_create_issue` using `{{summary.title}}` from a prior step | No LLM for this step. Cheap. | +| **3. Single-domain agent task** | `agent_task` with `tools: ["slack.*"]` only | One LLM, bounded toolset. | +| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named capabilities. | +| **5. Multi-domain agent task, broad** | `agent_task` with `tools: ["slack.*", "github.*", "linear.*"]` | One LLM, large toolset, most agentic. | +| **6. Composed plan** | `agent_task` (narrow) for thinking → `slack_post` + `linear_create_issue` for acting | Best cost-to-power ratio. | + +Shape 6 is the underrated one and the cost-and-speed answer. The agent +reasons once (Shape 3 or 4) and its structured output drives several +deterministic actions. This is roughly 5–10x cheaper and 3–4x faster than +forcing the agent to do everything (Shape 5) and produces the same outcome. + +**The NL generator's job is to propose Shape 6-style plans by default.** +The Review LLM flags proposals that use `agent_task` for steps a +deterministic action could handle. This is the discipline that keeps +automations cheap at scale. + +The user navigates the spectrum by intent (describing what they want), not +by mechanism — the shape selection is the engine's responsibility, not the +user's. + +--- + +## 5. Automation definition (Layer 3) + +This is the JSON the user writes (or the NL generator produces). Stored in +`automations.definition` as JSONB. + +### Top-level shape + +```jsonc +{ + "schema_version": "1.0", + "name": "Daily competitor digest", + "goal": "Summarize new competitor content and post to Slack", + + "inputs": { + "schema": { + "type": "object", + "required": ["since"], + "properties": { + "since": { "type": "string", "format": "date-time", + "default": "$last_fired_at" }, + "tags": { "type": "array", "items": { "type": "string" }, + "default": ["competitor"] } + } + } + }, + + "triggers": [ + { + "type": "schedule", + "config": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } + } + ], + + "plan": [ + { + "step_id": "research", + "action": "agent_task", + "config": { + "prompt": "Find documents tagged {{inputs.tags}} indexed since {{inputs.since}}. Return JSON with bullets and source_doc_ids.", + "tools": ["search_space.query", "search_space.fetch_document"], + "model": "anthropic/claude-sonnet-4-7", + "output_schema": { + "type": "object", + "required": ["bullets", "source_doc_ids"], + "properties": { + "bullets": { "type": "array", "items": { "type": "string" } }, + "source_doc_ids": { "type": "array", "items": { "type": "string" } } + } + } + }, + "output_as": "summary" + }, + { + "step_id": "deliver", + "action": "slack_post", + "config": { + "channel_id": "C0123", + "message_template": "*Competitor digest*\n\n{% for b in summary.bullets %}• {{b}}\n{% endfor %}" + } + } + ], + + "execution": { + "timeout_seconds": 600, + "max_retries": 2, + "retry_backoff": "exponential", + "concurrency": "drop_if_running", + "budget_cap_usd": 1.50, + "on_failure": [ /* steps to run if main plan fails after retries */ ] + }, + + "metadata": { "tags": ["digest"], "created_from_nl": true } +} +``` + +### Plan steps + +```jsonc +{ + "step_id": "...", // unique within plan + "action": "...", // references an ActionDefinition.type + "when": "{{ ... }}", // optional Jinja expr → bool; false = skip + "config": { ... }, // validated against action's config_schema + "output_as": "...", // binds output to this name for later steps + "max_retries": 0, // optional, overrides automation default + "timeout_seconds": 1200 // optional, overrides automation default +} +``` + +Steps run **sequentially**. No parallelism, no DAGs, no loops. If a user +needs branching, they use `when:` on multiple steps. If they need +parallelism or iteration, they use `agent_task` and let the agent reason +about it, or they compose automations through events (§7.5). + +--- + +## 6. Trigger contract (Layer 4) + +Three trigger types. That's the entire taxonomy. + +### `schedule` + +```python +TriggerDefinition( + type="schedule", + config_schema={ + "type": "object", + "required": ["cron", "timezone"], + "properties": { + "cron": { "type": "string" }, + "timezone": { "type": "string", "format": "iana-timezone" } + } + }, + payload_schema={ + "type": "object", + "properties": { + "fired_at": { "type": "string", "format": "date-time" }, + "scheduled_for": { "type": "string", "format": "date-time" }, + "last_fired_at": { "type": "string", "format": "date-time" } + } + } +) +``` + +Implementation: extends `app/utils/periodic_scheduler.py`, which already +reads connector sync schedules. Adds a second source — `automation_triggers +WHERE type='schedule'`. Same Celery Beat checker, two source tables. + +Minimum interval: 1 minute (the existing checker's resolution). The form +editor warns when users set intervals under 15 minutes that they probably +want an event trigger instead. + +### `webhook` + +```python +TriggerDefinition( + type="webhook", + config_schema={ + "type": "object", + "properties": { + "input_mapping": { + "type": "object", + "additionalProperties": { "type": "string" } + # values are JSONPath expressions + } + } + }, + # payload is whatever the POST body is; user-defined shape via mapping +) +``` + +Endpoint: `POST /api/v1/automations/{id}/fire`. Bearer token shown once, +hashed at rest, rotatable, revocable. Returns `202 Accepted` with the +created run's URL. Caller polls for status; we do not push callbacks in +v1 (a `callback_webhook` action can be added later). + +Idempotency: honors `Idempotency-Key` header or `idempotency_key` in body. +Dedups against runs in the last 24 hours. + +### `event` + +```python +TriggerDefinition( + type="event", + config_schema={ + "type": "object", + "required": ["event_type"], + "properties": { + "event_type": { "type": "string" }, # e.g. "drive.file_added" + # or "surfsense.podcast.generated" + "filters": { "$ref": "#/definitions/filter_expression" } + } + } + # payload shape is documented per event_type in a separate registry +) +``` + +**Events absorb both connector events and internal SurfSense events.** A +file added to Drive and a podcast finishing in SurfSense are both events +in the same `domain_events` table, both subscribable by automations, both +matched by the same dispatcher code. The engine doesn't distinguish. + +### Filter grammar + +Filters are JSON-structured operators, not expressions. This is the one +place we deliberately don't use Jinja, because filters run on a hot path +(every event matched against every subscribing trigger) and structured +filters can be indexed and short-circuited. + +Vocabulary: +- Equality: `equals`, `not_equals` +- String: `starts_with`, `ends_with`, `contains`, `regex` +- Numeric: `gt`, `gte`, `lt`, `lte` +- Set: `in`, `not_in` +- Existence: `exists` +- Composition: `$and`, `$or`, `$not` + +Inspired by AWS EventBridge and MongoDB query syntax. The filter grammar +itself is published as a JSON Schema, so users get inline error messages. + +--- + +## 7. Runtime components + +Each component is distinct, replaceable, and has one job. + +### 7.1 Dispatcher + +What it does: matches firing triggers to automations, creates `AutomationRun` +rows, enqueues executor tasks. + +For schedule triggers: Celery Beat polls the trigger table, computes due +ones, fires. + +For webhook triggers: the FastAPI handler is the dispatcher entry point. +Validates token, runs input_mapping, creates run. + +For event triggers: subscribes to the `domain_events` table. For each new +event, evaluates all matching triggers' filters, fires the matches. + +Common path (after a trigger has fired): +1. Resolve `inputs` from trigger payload and defaults +2. Validate resolved inputs against the automation's input schema +3. **Cost estimate** — sum capabilities' `cost_estimate(args)` for the plan; + refuse if exceeds `budget_cap_usd` +4. **Idempotency check** — dedup against existing pending/running runs +5. **Snapshot the resolved definition** into the run row (immutable history) +6. Enqueue executor task on the appropriate Celery queue (per + `expected_duration_seconds`) + +### 7.2 Executor + +What it is: **a Celery task wrapping a single function that walks a plan +step by step.** Not an agent, not a workflow engine, not a scheduler. A +loop with bookkeeping. Maybe 200 lines. + +```python +async def execute_run(run_id: int) -> None: + run = load_run(run_id); run.status = "running"; save(run) + context = build_run_context(run) + step_outputs = {} + + for step in run.plan: + if step.when and not evaluate_predicate(step.when, context | step_outputs): + record_step_skipped(run, step); continue + + resolved_config = render_config(step.config, context | step_outputs) + action = action_registry.get(step.action) + validate(resolved_config, action.config_schema) + + try: + result = await with_retries( + action.handler, + ctx=build_action_context(run, action), + args=resolved_config, + policy=step.retry_policy or run.execution.retry_policy, + ) + validate(result, step.output_schema) + if step.output_as: + step_outputs[step.output_as] = result + record_step_succeeded(run, step, result) + except Exception as e: + record_step_failed(run, step, e) + await run_on_failure(run, e) + return + + run.status = "succeeded"; save(run) + publish_event("automation.run.succeeded", run) # see §7.5 +``` + +Intelligence lives **inside handlers**, not in the executor. The most +intelligent handler is `agent_task`, which spins up a LangGraph Deep Agent +for one step and returns when the agent finishes. The executor sees a +validated dict come back; it doesn't know that step was "smart." + +### 7.3 Action handlers + +One handler per `ActionDefinition.type`. Receives `(ctx, args)`, returns +a dict matching `output_contract` (or matching the user-declared +`output_schema` for dynamic-output actions like `agent_task`). + +Handlers handle their own credential resolution via `ctx.resolve_credentials`. +They do not know about retries, timeouts, or budget caps — those are the +executor's concern. + +### 7.4 Template engine + +#### Why it exists + +Most fields in an automation definition contain literal strings the user +authored once — but the actual rendered value has to change per run, because +it includes data from the trigger payload or from prior step outputs. The +template engine is what turns `"Daily digest for {{run.started_at}}"` into +`"Daily digest for 2026-05-26"` at run time. + +Three fields use it: +- `*_template` strings in tight action configs (Slack messages, email bodies, + Linear titles, etc.) +- `prompt` in `agent_task` configs (so the agent sees resolved values, not + `{{...}}` placeholders) +- `when:` step predicates (which need to evaluate to a boolean) + +#### Public interface + +Single module, ~80 lines. Three public functions — everything else in the +engine routes through these: + +```python +def render_template(template: str, context: dict) -> str: ... +def evaluate_predicate(expression: str, context: dict) -> bool: ... +def build_run_context(run, step_outputs) -> dict: ... +``` + +Backed by Jinja2's `SandboxedEnvironment`. The whole module is the seam: if +the template language is ever swapped, only this file changes. + +#### Security architecture: allowlist by default + +`SandboxedEnvironment` starts empty. A freshly-created instance gives a +template access to: +- Variables in the context dict we pass in (`run`, `inputs`, prior step + outputs) +- Public (non-underscore) attributes of those variables +- Jinja's built-in control flow (`{% if %}`, `{% for %}`, `{% set %}`) + +Nothing else. No Python builtins, no modules, no I/O, no network, no +filesystem. Everything beyond the above must be **explicitly registered.** +This is the structurally important property: anything we didn't add is +inaccessible. The risk surface equals the size of what we registered. + +The three sandbox rules that enforce this: +1. **Attribute access is filtered** — names starting with underscore are + rejected. This blocks the entire family of `{{x.__class__.__mro__...}}` + Python escape paths in one rule. +2. **Globals are allowlist-only** — `open`, `eval`, `exec`, `__import__`, + `getattr`, every module name, are all absent unless we register them. + We register zero globals. +3. **Unsafe callables are blocked** — `str.format` and `str.format_map` + specifically (due to CVE-2016-10745), plus anything marked + `unsafe_callable`. + +#### What we register, exactly + +- **Filters: a curated 15**, no more. `join`, `length`, `default`, `upper`, + `lower`, `truncate`, `tojson`, `date`, `replace`, `trim`, `slugify`, + `first`, `last`, `sort`, `reverse`. Each one is audited for what it does + with its input; none of them takes a callable, runs `eval`, or reaches + into Python objects beyond simple data transformation. +- **Globals: none.** +- **Tests: only the safe built-ins** (`defined`, `none`, `number`, `string`, + `mapping`, `sequence`, `boolean`). + +Adding a new filter requires a deliberate code change and review: does this +filter do anything dangerous with its input? If yes, don't add it. The list +only grows by audited additions. + +#### Runtime limits (defense in depth) + +The sandbox handles the attack surface inside the template language. Three +additional limits handle resource exhaustion that the language permits but +the runtime shouldn't tolerate: + +- **Template source length capped at 8 KB.** Checked before parsing. +- **Render time capped at 100 ms per render.** Implemented via a watchdog + thread; renders that exceed are killed and the step fails. Catches + `{% for i in range(10**9) %}` and nested loop bombs. +- **Output size capped at 1 MB.** A small template can produce a multi-GB + string via `{{ 'A' * 10**8 }}`-style multiplication; this catches it. + +Plus `StrictUndefined`: any reference to a missing variable raises +immediately rather than silently rendering empty, so misconfigurations +fail fast. + +#### Threat model and residual risk + +The trust model from day one is: + +- Templates are generated by an LLM from a user's natural-language input + (see §10), or written/edited by humans in the editable form +- A second LLM reviews the proposal and produces a plain-language summary + plus flagged anomalies for the user +- The user reviews and approves before the automation runs +- The Generator LLM's input is scoped (user prompt + schema + registry + only — no arbitrary document content), minimizing prompt-injection paths + +The sandbox + runtime limits + curated filter list protect against the +malformed-template attack. Human review protects against the +semantically-malicious-but-syntactically-valid attack. These are +complementary layers, not redundant. + +Known residual risks, each genuinely small: + +- **Future Jinja CVEs.** Historical sandbox bypasses have existed and + been patched. This is a generic third-party-dependency risk, comparable + to bugs in any other library we rely on. Mitigation: subscribe to + security advisories, ship updates within a week of disclosure. +- **Side channels via prompts to LLMs.** A template that renders into a + prompt can attempt prompt injection of the agent at run time. This is + not a sandbox concern but a separate concern in `agent_task`'s design. +- **Operator deployments with long-lived secrets in worker env vars.** + Mitigation: credentials fetched per-handler-per-call via + `ActionContext.resolve_credentials`, never pre-loaded into worker + env vars accessible to templates. + +The sandbox-with-allowlist architecture means **the attack surface +equals the set of things we registered.** With zero globals registered +and 15 audited filters, the surface is small, bounded, and reviewable. +This is the structural property that makes the architecture sound, and +it doesn't depend on hypothetical assumptions about who authors templates. + +#### Pre-Phase-5 gate + +One trust-model change is documented in the roadmap: **Phase 5 introduces +template sharing across SearchSpaces** (automation templates as +exportable, importable artifacts). At that point, the *approver* of a +template (the original author) is no longer the *runner* (the importer). +The "human reviews before save" mitigation breaks down because the +reviewer doesn't bear the risk. + +Before Phase 5 ships, this needs an explicit re-approval flow: importing +a template triggers a fresh review pass by the importing user, with the +flagged-anomalies output prominently displayed, and the import cannot +complete without explicit per-template approval. + +This is a UX/flow decision, not a template-language migration. Jinja +itself stays; what changes is the approval workflow at the import boundary. + +#### The `run.*` namespace exposed in every template + +``` +run.id, run.started_at, run.automation_id, run.automation_name, +run.automation_version, run.trigger_type, run.trigger_id, +run.search_space_id, run.creator_id, run.attempt, +run.failed_step_id, run.error.* (only in on_failure context) +``` + +#### Default value rendering + +Non-string template values render as JSON by default (via the `finalize` +hook): lists become `["a", "b"]`, dicts become `{"k": "v"}`, datetimes +become ISO 8601. The `| join`, `| length`, `| tojson` filters give explicit +control. Strings render as themselves with no quoting. `None` renders as +empty string in templates, as `null` in JSON contexts. + +### 7.5 Event bus + +`domain_events` table, polled by Celery Beat alongside the existing +scheduler. Both connector events and internal SurfSense events publish to +it. Both are consumed by the dispatcher's event-trigger subscriber. + +**Automations themselves publish events.** Successful and failed runs emit +`automation.run.succeeded` / `automation.run.failed` events with the run +metadata. This makes automations composable through events — chain them by +subscribing one automation's event trigger to another's run event. No new +mechanism; the trigger filter and event publishing already exist. + +Upgrade path documented: when throughput or latency demands it, replace +PostgreSQL polling with Redis Streams. The `events.publish()` and +`events.subscribe()` interfaces stay the same. Nothing else changes. + +--- + +## 8. Cross-cutting concerns + +### Concurrency policy + +Per-automation `concurrency` field controls what happens when a new fire +occurs while a previous run is still running: + +- `drop_if_running` — silently skip the new fire +- `queue` — execute serially, in arrival order +- `allow_parallel` — start a new run independently + +The dispatcher enforces this before enqueueing. + +### Retry policy + +Three fields, per-automation defaults with optional per-step overrides: +- `max_retries`: integer, 0–10 +- `retry_backoff`: `none` | `linear` | `exponential` +- `timeout_seconds`: integer + +Retries on: +- Capability handler exceptions +- Output schema validation failures (for dynamic-output actions, the + validation error is fed back to the LLM in the retry) + +Not retries: +- `when:` evaluation failures (these are user errors, surface immediately) +- Input validation failures (caught at dispatch, never reach the executor) + +### Budget enforcement + +`budget_cap_usd` is per-run. The dispatcher refuses to enqueue if estimated +cost exceeds it. The executor kills the run if accumulated cost crosses it +mid-flight (the LLM ops handler reports tokens consumed back to the +executor between calls). + +### On-failure handlers + +`execution.on_failure` is a list of steps that run after the main plan has +failed and all retries are exhausted. Same step shape as the main plan. +Cannot have their own `on_failure`. See `run.error.*` in the run context. + +### Artifacts + +Actions that produce artifacts declare `produces_artifacts: list[ArtifactSpec]`: + +```python +@dataclass +class ArtifactSpec: + kind: str # "audio", "document", "image", "data" + retention: str # "transient" | "default" | "permanent" + visibility: str # "private" | "search_space" | "shared" +``` + +The engine handles storage (writes to SurfSense's existing object storage), +URL generation (signed, scoped to the run's permissions), and cleanup (a +nightly Celery Beat task deletes expired artifacts). + +### Duration classes and queue routing + +Capabilities declare `expected_duration_seconds`. The dispatcher routes +runs to Celery queues based on the longest-duration step: +- < 10s → `automations_fast` +- 10s – 5min → `automations_medium` +- 5min – 1hr → `automations_long` + +Operators scale each queue's worker pool independently. A future "very +long" queue is a config change, not a contract change. + +--- + +## 9. Data model + +Six tables. All scoped by `search_space_id` for RBAC. + +The first four (`automations`, `automation_triggers`, `automation_runs`, +`domain_events`) are the engine's own state. The last two +(`mcp_connections`, `mcp_tools`) hold the durable knowledge that backs +MCP-derived capabilities — see §3 for the lifecycle rationale. + +### `automations` + +| field | type | notes | +| ----------------- | ----------------------------------- | -------------------------------------------------------------------------- | +| `id` | int PK | | +| `search_space_id` | FK → `search_spaces.id` | | +| `created_by` | FK → `users.id` | runs execute as this identity | +| `name` | str | | +| `description` | str | | +| `status` | enum | `active`, `paused`, `archived` | +| `definition` | jsonb | the editable structured spec | +| `version` | int | bumped on every edit | +| `created_at` / `updated_at` | timestamps | | + +### `automation_triggers` + +| field | type | notes | +| --------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | +| `id` | int PK | | +| `automation_id` | FK | | +| `type` | enum: `schedule`, `webhook`, `event` | | +| `config` | jsonb | validated against trigger's `config_schema` | +| `enabled` | bool | | +| `secret_hash` | str / null | for webhook bearer tokens | +| `last_fired_at` | timestamp | | + +### `automation_runs` + +| field | type | notes | +| ----------------- | ---------------------------------------------------------------------------- | -------------------------------------------------- | +| `id` | int PK | | +| `automation_id` | FK | | +| `trigger_id` | FK / null | null = manual via UI | +| `status` | enum | `pending`, `running`, `succeeded`, `failed`, `cancelled`, `timed_out` | +| `definition_snapshot` | jsonb | the definition as it was when this run fired | +| `trigger_payload` | jsonb | | +| `resolved_inputs` | jsonb | | +| `step_results` | jsonb | array of per-step results with timing | +| `output` | jsonb / null | | +| `artifacts` | jsonb | references to created artifacts | +| `error` | jsonb / null | | +| `cost_usd` | decimal | accumulated cost | +| `started_at` / `finished_at` | timestamps | | +| `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | + +### `domain_events` + +| field | type | notes | +| ----------------- | ----------- | -------------------------------------------------- | +| `id` | UUID PK | | +| `search_space_id` | FK | scoping | +| `event_type` | varchar | e.g. `drive.file_added`, `automation.run.succeeded` | +| `source_id` | varchar | which connector/automation/etc. produced it | +| `payload` | jsonb | matches the event type's documented schema | +| `created_at` | timestamp | | +| `consumed_by` | jsonb | array of consumer_ids, for tracking + replay | +| `expires_at` | timestamp | auto-cleanup after 7 days | + +### `mcp_connections` + +Persistent record of MCP server connections per SearchSpace. + +| field | type | notes | +| ------------------- | ----------- | -------------------------------------------------- | +| `id` | UUID PK | | +| `search_space_id` | FK | scoping | +| `server_url` | text | the MCP server's endpoint | +| `transport` | text | `"http"`, `"stdio"`, etc. | +| `name` | text | human-readable label (e.g., "Slack — Acme") | +| `access_token` | bytea | encrypted at rest | +| `refresh_token` | bytea | encrypted at rest | +| `expires_at` | timestamp | for OAuth tokens | +| `last_harvested_at` | timestamp | when tool list was last refreshed | +| `created_at` | timestamp | | +| `created_by` | FK → users | | + +### `mcp_tools` + +The tool list each connected MCP server exposes. Acts as the durable +source for MCP capabilities — definitions reference `mcp_tools` rows by +qualified name, and worker processes lazily build handler closures from +this state. + +| field | type | notes | +| --------------- | ----------- | ------------------------------------------------ | +| `id` | UUID PK | | +| `connection_id` | FK → `mcp_connections.id` ON DELETE CASCADE | | +| `name` | text | the tool name reported by the MCP server | +| `description` | text | description for the NL generator and form editor | +| `input_schema` | jsonb | JSON Schema for tool arguments | +| `output_schema` | jsonb | JSON Schema for tool results | +| `side_effects` | text[] | inferred from MCP hints + naming + admin override | +| UNIQUE | | (connection_id, name) | + +NL drafts are **not** a core table. They live in a generic short-TTL store +(Redis or a transient table) when the NL flow is built in Phase 3. + +--- + +## 10. NL authoring flow + +**This is how the system is intended to be used from day one, not just a +Phase 3 addition.** The product surface is: user describes intent in natural +language, LLM produces a structured proposal, user reviews and edits in an +auto-generated form, then saves. Hand-authoring JSON directly is supported +but is not the primary path. + +This shapes the trust model. Templates are LLM-generated from day one, not +hand-written by power users. The mitigation is human-in-the-loop review, +not "trusted authors only." + +### Pass 1: Proposal generation + +User provides natural-language input. The Generator LLM is given: +- The full schema set (input schema for definition, registry of action + types with their config_schemas, registry of trigger types, available + capabilities for this SearchSpace, list of allowed Jinja filters) +- A tool to list available connectors, channels, and other SearchSpace + resources, so it doesn't invent names that don't exist +- A few-shot set of examples + +**Scoped input.** The Generator does *not* receive arbitrary SearchSpace +document content. Its context is the user's prompt plus the schema and +registry information. This minimizes the prompt-injection surface — there's +no document text in the context for an attacker to seed instructions into. + +If a user wants document-aware generation later ("create an automation +that processes documents like this one"), that's a deliberate feature +extension with its own prompt-injection mitigations, not the default flow. + +Output: a structured proposal matching the automation definition schema. + +### Pass 2: Deterministic validation + +Server-side, before the proposal reaches the user: +- Validate against JSON Schema (shape correctness) +- Verify every capability referenced exists in the registry (resource existence) +- Verify every connector/channel/resource referenced exists in this SearchSpace +- Validate every template against the sandbox's allowlist (no underscore + attributes, no unregistered filter names, length under cap) + +Failures here are deterministic errors, not warnings. A proposal that +references a non-existent capability or includes a template using +`{{x.__class__}}` is rejected before the user sees it; the Generator is +re-prompted with the validation error and asked to fix the proposal. + +### Pass 2.5: Review pass + +A second LLM call — the **Review LLM** — examines the validated proposal and +produces two outputs for the user: + +1. **A plain-language summary** of what the automation will do, in business + terms. "This automation will run every weekday at 9am. It reads documents + in this SearchSpace tagged 'competitor' that were indexed since the last + run, asks an agent to summarize them as 5 bullets, and posts the summary + to your #engineering-standup Slack channel. Estimated cost: $0.40 per + run." + +2. **A "things worth checking" list** flagging anything unusual: + - Templates with unusual attribute paths or filter usage + - Prompts containing instructions that look more like commands than + descriptions ("ignore previous instructions" style) + - Action sequences that touch external systems without obvious benefit + to the user + - Cost estimates that seem high relative to the goal + - References to capabilities the user hasn't used before + - Schedules tighter than 15 minutes (likely should be event triggers) + +The Review LLM is a **UX layer** that makes review actually useful. It is +**not a security boundary.** The deterministic controls (sandbox, runtime +limits, schema validator) are the security boundaries. The Review LLM +helps users catch their own intent mismatches and surfaces anomalies for +attention, but the sandbox would block dangerous templates even if the +Review LLM missed them. + +This separation is important: two probabilistic controls compounding can +create a false sense of security. The Review LLM is explicitly framed in +the architecture as helper, not gatekeeper. + +### Pass 3: Editable review + +The user lands on a form pre-filled with the proposal. The page shows: +- The plain-language summary from the Review pass +- The flagged items, prominently displayed near the relevant fields +- The full editable form, auto-generated from the JSON Schemas +- Cost estimate and impact summary (which external systems get touched) + +**Every field is editable.** Clarifications appear as required fields. +Templates are shown in code-styled fields with syntax highlighting and the +filter palette visible. The user can edit any field; saving re-runs Pass 2 +(deterministic validation) before persisting. + +Hitting **Save** promotes the proposal to an `automation` row. + +### Editing existing automations + +NL editing of an existing automation is a patch operation: the Generator +LLM receives the current definition plus the NL instruction and produces a +modified proposal. The same Pass 2 (validation) and Pass 2.5 (review) run +against the modified version, and the user reviews the diff before saving. +Existing run history is unaffected — only future runs use the new version. + +### Why human-in-the-loop is non-negotiable + +The Generator LLM, the Review LLM, and the sandbox are three layers of +defense against malformed or malicious proposals. The human approval step +is the fourth and most important layer. It exists because: + +- LLMs can be prompt-injected; humans can spot text that asks them to + ignore instructions +- LLMs can produce confident-but-wrong proposals; humans can catch + semantic mismatches between intent and output +- The cost of a bad automation running unattended is high; the cost of a + user clicking "approve" after reading is low + +The architecture must never offer "auto-approve" or "skip review" options +for LLM-generated proposals. Save requires human action on the proposal, +always. + +--- + +## 11. Repository layout + +``` +surfsense_backend/app/ +├── automations/ # NEW: the engine +│ ├── __init__.py +│ ├── models.py # SQLAlchemy models for 6 tables +│ ├── schemas.py # Pydantic schemas (definition envelope, etc.) +│ ├── routes.py # FastAPI router (/api/v1/automations) +│ ├── service.py # CRUD + business logic +│ ├── dispatcher.py # trigger matching, cost check, run creation +│ ├── executor.py # the Celery task that runs a plan +│ ├── templating.py # Jinja sandbox + filters +│ ├── events.py # publish/subscribe for domain_events +│ ├── filters.py # JSON filter grammar evaluator +│ ├── actions/ +│ │ ├── registry.py +│ │ ├── agent_task.py +│ │ ├── transform_data.py +│ │ ├── slack_post.py +│ │ ├── send_email.py +│ │ ├── notification.py +│ │ └── (more in Phase 5: podcast_generation, report_generation, ...) +│ ├── triggers/ +│ │ ├── registry.py +│ │ ├── schedule.py # Celery Beat hookup +│ │ ├── webhook.py # /fire endpoint +│ │ └── event.py # subscribes to domain_events +│ ├── capabilities/ +│ │ ├── registry.py +│ │ ├── native.py # native capability registrations +│ │ ├── mcp_harvester.py # registers MCP tools as capabilities (Phase 4) +│ │ └── (LLM ops registered alongside) +│ └── nl/ # Phase 1 — primary user path +│ ├── generator.py # Generator LLM +│ ├── reviewer.py # Review LLM (summary + flagged items) +│ ├── validator.py # deterministic schema + resource checks +│ └── prompts.py # system prompts for both LLMs +│ +├── utils/ +│ └── periodic_scheduler.py # EXTENDED to scan automation_triggers +│ +└── alembic/versions/ + └── NN_add_automation_tables.py + +surfsense_web/app/(routes)/ +└── automations/ # NEW: UI + ├── page.tsx # list + ├── new/page.tsx # NL input + draft preview (Phase 1) + ├── [id]/page.tsx # editor (auto-generated forms) + └── [id]/runs/page.tsx # run history, streamed via Electric SQL +``` + +--- + +## 12. Phased delivery + +Each phase delivers something usable. Each de-risks the next. **NL authoring +is the primary user path from Phase 1** — what evolves across phases is +which actions and triggers are available, not whether users can describe +automations in natural language. + +### Phase 1 — Engine MVP with NL authoring +- 4 tables + Alembic migration +- Capability registry with native capabilities (`search_space.query`, + `search_space.fetch_document`, `agent.run`) +- `agent_task` action only +- `schedule` trigger + manual "Run now" endpoint +- Executor with retries, timeouts, budget caps +- Template engine (Jinja sandbox + 15 filters + 4 runtime limits) +- **NL authoring flow**: Generator LLM, deterministic validator, + Review LLM, editable form +- Run history UI with Electric SQL streaming + +**After Phase 1**: a user can describe an automation in natural language, +review the proposal (with summary + flagged anomalies), edit any field, +save, and watch it run on a schedule. The Claude Routines value +proposition, on SurfSense's data, with NL-first authoring. + +### Phase 2 — Webhooks and delivery +- `webhook` trigger with per-automation bearer tokens +- Tight actions: `slack_post`, `send_email`, `notification` +- `transform_data` action +- `on_failure` hooks +- Step-level retry/timeout overrides +- Concurrency policy enforcement + +**After Phase 2**: external systems can drive automations, results go +somewhere humans see, complex pipelines have proper error handling. + +### Phase 3 — NL authoring polish +- NL patch flow for editing existing automations (diff-based) +- Conversational refinement during proposal review ("change the schedule + to weekdays only," "add a Slack notification on failure") +- Improved Review LLM coverage (more anomaly patterns, cost-relative-to- + goal heuristics) +- Saved prompt templates and starter examples + +**After Phase 3**: NL authoring is the polished primary surface; edit +flows are conversational rather than form-only. + +### Phase 4 — Event triggers +- `domain_events` table and `events.py` module +- Indexing pipeline publishes `connector.*` events (smallest change — just + add publish calls to the existing flow) +- Automations publish `automation.run.*` events on completion +- `event` trigger with filter grammar +- MCP capability harvester (so MCP-backed events and tools both work) + +**After Phase 4**: "do X when Y happens" automations work, including +automation-chaining through events. + +### Phase 5 — Wrapping existing features and sharing +- Wrap existing SurfSense capabilities as actions: `podcast_generation`, + `report_generation`, `indexing_sweep` +- Artifact lifecycle implementation +- `expected_duration_seconds` based queue routing (split `automations_long` + from `automations_default`) +- **Automation templates** (shareable, exportable, importable) — with + the import re-approval flow that handles the approver-≠-runner trust + shift documented in §7.4's pre-Phase-5 gate +- Cross-automation composition examples in the docs + +**After Phase 5**: every existing SurfSense capability is automatable +without any per-feature code, and automations can be shared between +SearchSpaces and users. + +--- + +## 13. Decisions locked + +For reference — every decision made through the design process, in one +place. + +### Foundations +1. ✅ JSON Schema 2020-12 is the single schema language for everything +2. ✅ Definition is the program; infrastructure is the interpreter +3. ✅ List of steps (not single action) in the plan, with `output_as` chaining +4. ✅ One capability registry serving native + MCP + LLM operations through the same interface +5. ✅ Capability IDs do not leak handler kind (`slack.post_message`, not `mcp.slack.post_message`) +6. ✅ Name-based resolution: definitions reference actions and capabilities by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. +7. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) + +### Trigger taxonomy +8. ✅ Three trigger types: `schedule`, `webhook`, `event` +9. ✅ Events absorb both connector events and internal SurfSense events +10. ✅ Filter grammar is JSON-structured operators (not Jinja) + +### Templating cluster +11. ✅ Jinja2 `SandboxedEnvironment` for templates and `when:` predicates — but with the explicit understanding that the sandbox is an allowlist-by-default architecture, not a denylist +12. ✅ Zero globals registered. Curated 15 filters only, each audited for safe behavior with hostile input. List grows only by reviewed addition +13. ✅ Four runtime mitigations: `StrictUndefined`, 8 KB template source cap, 100 ms render time cap (watchdog-enforced), 1 MB output size cap +14. ✅ Non-string template values render as JSON by default +15. ✅ Fixed `run.*` namespace, documented +16. ⏸ **Pre-Phase-5 gate**: template sharing across SearchSpaces breaks the approver-equals-runner trust model. Mitigation is a re-approval flow at the import boundary (UX-level), not a template-language migration. Jinja itself stays. + +### Execution +17. ✅ Executor is a Celery task wrapping a sequential loop — not an agent +18. ✅ `when:` is optional per step; false = skipped (not failed) +19. ✅ No DAGs, no parallelism, no loops — composition via agent_task or events +20. ✅ `on_failure` part of execution policy from v1 +21. ✅ Step-level retry and timeout overrides +22. ✅ Budget cap enforced pre-enqueue and mid-flight + +### Components +23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable +24. ✅ Side effects are a set, including `USER_VISIBLE` +25. ✅ `expected_duration_seconds` integer drives queue routing +26. ✅ `produces_artifacts` is a list of `ArtifactSpec`, not a bool +27. ✅ Output schemas recommended on `agent_task`; editor warns when missing + +### Event bus +28. ✅ `domain_events` table for v1, with upgrade path to Redis Streams +29. ✅ Automations publish run events for composability +30. ✅ Publish/subscribe behind interface — no direct table access elsewhere + +### Capability storage (two-tier persistence) +31. ✅ Native capabilities registered in-memory at startup from the codebase. Identical across all workers. +32. ✅ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables. Survives restarts. +33. ✅ MCP handler closures built lazily per worker from database state. Worker-local cache, rebuilt on demand. +34. ✅ MCP server tool list re-harvested on a schedule (default: daily) and on user request. +35. ✅ MCP tools harvested into the capability registry at connection time +36. ✅ Side effects inferred from MCP hints + naming + admin overrides +37. ✅ MCP tools callable directly (no agent required) when caller knows args + +### Credentials +38. ✅ Credentials never appear in the automation definition — only connection IDs do +39. ✅ Credentials never appear in the LLM's context — the host holds them and uses them on the LLM's behalf +40. ✅ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment +41. ✅ Tokens encrypted at rest in the database; refresh handled automatically by `ActionContext.resolve_*_client` + +### NL authoring +42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary +43. ✅ Generator LLM produces JSON; deterministic schema + resource validation runs before user sees the proposal +44. ✅ Review LLM produces plain-language summary + flagged anomalies for the user — UX layer, not a security boundary +45. ✅ Generator LLM's input is scoped (user prompt + schema + registry only); arbitrary document content is not fed in +46. ✅ Human approval is required before save — no auto-approval option, ever +47. ✅ Every field editable in the proposal; unresolved questions surface as clarifications +48. ✅ NL drafts are transient storage, not a core table + +### Data model +49. ✅ Six tables total — four for engine state, two for MCP persistence +50. ✅ Run rows snapshot the definition (immutable history) +51. ✅ All entities scoped by `search_space_id` for RBAC +52. ✅ Editing an automation bumps `version`; existing runs unaffected + +--- + +## 14. Open questions deferred to implementation + +None of these block design; they're decisions a developer will make in +context, with the principle from §1 as their guide. + +- Exact retry backoff formulas (multipliers, jitter, ceilings) +- Webhook signature verification standards (HMAC scheme, header naming) +- Whether to support inline JSON Schema `$ref` to external schemas, or + inline everything +- Specific CDN/storage backend choices for artifacts (probably + whatever SurfSense already uses for podcasts) +- Rate limits per SearchSpace and per user +- Audit log retention policy + +--- + +## 15. Why this is ready to build + +This document satisfies five tests: + +1. **The four worked examples** (digest, CI webhook, file-added-trigger, + weekly podcast) all express cleanly in the contract without special + cases. Each one was used to find gaps before the gaps reached code. + +2. **The audit pass identified six refinements**, all incorporated. No + pending audit items. + +3. **Every decision points back to the principle from §1.** When a future + feature request lands, "does it belong in the definition or in the + engine?" gives a clear answer. + +4. **The build is staged** so Phase 1 ships in weeks, not months, and + each subsequent phase delivers user value while de-risking the next. + +5. **Existing SurfSense infrastructure is reused**, not paralleled. Celery + Beat, PostgreSQL/JSONB, Electric SQL, SQLAlchemy/Alembic, the existing + `tools/registry.py` pattern, the existing Search Space scoping — all + continue to do what they already do. The automation engine is a new + directory, not a new system. + +The next document a developer needs is the Pydantic models and JSON +Schemas spelled out concretely. Those follow mechanically from this plan. + +--- + +*Sources consulted: Claude Code Routines documentation; NousResearch/hermes- +agent (cron and skills subsystems); n8n documentation on node types and +workflow data model; the SurfSense repository and DeepWiki architecture +notes (FastAPI + Celery Beat + Electric SQL + LangGraph Deep Agents + +Search Space RBAC); Model Context Protocol specification for capability +harvesting; AWS EventBridge for filter grammar; workflow-pattern +literature (van der Aalst et al.) for the trigger / action / concurrency +vocabulary.* From 16b661862930266418fb1bfda6b75e7fbf8f62c7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:33:10 +0200 Subject: [PATCH 017/133] docs(automation): trim Capability dataclass to v1-minimum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduce the §3 Capability dataclass from ten fields to five: id, description, input_schema, output_schema, handler. Removed fields (name, required_credentials, side_effects, expected_duration_seconds, cost_estimate) are reintroduced only when a concrete consumer feature demands them. The v1 invariant is that a Capability is a typed, named, callable unit and every consumer (executor, agent tool layer, future HTTP API) sees the same five-field shape. --- automation-design-plan.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index 072f7ad99..ca7506446 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -76,18 +76,39 @@ is the atomic unit of "things automations can do." @dataclass class Capability: id: str # "slack.post_message" - name: str # "Post Slack message" - description: str # for the NL generator + description: str # for the NL generator + UI label input_schema: dict # JSON Schema output_schema: dict # JSON Schema - required_credentials: list[CredSpec] # what creds the handler needs - side_effects: set[SideEffect] # READ, WRITE, EXTERNAL_WRITE, - # COST_INCURRING, USER_VISIBLE - expected_duration_seconds: int # estimate or upper bound - cost_estimate: Callable[[dict], Decimal] # f(input) → estimated USD handler: AsyncHandler ``` +### v1-minimum: five fields, nothing else + +The Capability is **deliberately five fields in v1**. Every additional field +that earlier drafts considered (`name`, `required_credentials`, +`side_effects`, `expected_duration_seconds`, `cost_estimate`) has been +removed until a concrete consumer feature demands it. Authoring stays cheap +and the registry stays trivial to introspect: + +- `name` → folded into `description`. The UI can render a short label from + the first line of `description` or fall back to `id`. No separate field + needed in v1. +- `required_credentials` → returns when external-credential capabilities + ship (Phase 2). v1 capabilities run server-side with app config; nothing + to declare. +- `side_effects` → returns when RBAC inside automations or + `READ_ONLY`-only agent tool gating arrives. v1 capabilities are + hand-picked and all trusted code. +- `expected_duration_seconds` → returns when multi-queue routing ships. + Single Celery queue in v1. +- `cost_estimate` → never returns as a declared field; cost is measured + per run from a ledger, aggregated per Capability, and surfaced as a + historical average. Pre-flight checks are deferred. + +The runtime invariant: a Capability is **a typed, named, callable thing +the system can do.** Every consumer (executor, agent tool layer, future +HTTP API) sees the same five-field shape and uses it the same way. + ### Where capabilities live: a two-tier registry The capability registry has different storage requirements for different From b029c090bdfa7cbcadfab346e106a5599237ebbd Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:34:03 +0200 Subject: [PATCH 018/133] docs(automation): defer MCP integration to Phase 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the two-tier registry, MCP database schema, harvester pseudocode, and the lazy per-worker closure cache from §3. v1 ships with a single in-memory native registry; the MCP design is reintroduced in Phase 4 along with the rest of the integration-tooling surface. The deferral is additive: the v1 registry interface is the same callable surface a Phase-4 MCP harvester will register into. No design rewrite between phases. --- automation-design-plan.md | 155 ++++++-------------------------------- 1 file changed, 23 insertions(+), 132 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index ca7506446..07eca2049 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -109,143 +109,34 @@ The runtime invariant: a Capability is **a typed, named, callable thing the system can do.** Every consumer (executor, agent tool layer, future HTTP API) sees the same five-field shape and uses it the same way. -### Where capabilities live: a two-tier registry +### Where capabilities live (v1) -The capability registry has different storage requirements for different -kinds of capabilities. **Native capabilities and MCP capabilities have -different lifecycles**, so they're persisted differently: +In v1, the capability registry is a single in-memory dict, populated at +process startup from native registrations in +`automations/registries/capabilities/`. Identical across all workers. +No database persistence, no closures rebuilt per worker. -| Tier | What's there | Where it lives | Lifetime | -| --- | --- | --- | --- | -| **Native** | Capabilities defined in SurfSense's codebase (`search_space.query`, `agent.run`, etc.) | In-memory dict, populated at startup from `automations/capabilities/native.py` | Process lifetime, identical across all workers | -| **MCP (durable)** | The fact that this SearchSpace has connected to this MCP server, the tool list it exposes, credentials | PostgreSQL: `mcp_connections` and `mcp_tools` tables | Persistent across restarts and across time | -| **MCP (cached)** | Handler closures wrapping `(connection_id, tool_name)` | Per-worker in-memory cache, lazily built from the database on first reference | Process lifetime, rebuilt on demand | +### MCP integration — deferred to Phase 4 -The reason this matters: **a user connects an MCP server on Monday, writes -an automation on Tuesday, the automation runs on Friday.** Between Monday -and Friday, workers will restart many times. Any state that only lives in -worker memory is gone. The closures generated at connection time would -not survive. +The earlier two-tier registry (native + MCP-derived), the +`mcp_connections` / `mcp_tools` tables, the harvester, and the lazy +per-worker closure cache are **deferred to Phase 4** along with the +rest of the integration-tooling surface. They are removed from v1 +because: -So we split persistence by lifecycle: +- v1 has no external connector capabilities (no Slack, Notion, Drive, + etc.). The only capabilities that will ship are server-side helpers + (search-space query / fetch) plus the loose `agent_task` action. +- Without external connectors, the lifecycle mismatch that motivates + the two-tier design (connect Monday, run Friday, workers restarted + in between) doesn't arise. A startup-time dict is sufficient. +- Phase 4 reintroduces this design as-is — the registry interface in + v1 is the same callable surface a Phase-4 MCP harvester will register + into. The deferral is additive, not a different design. -- Native capability handlers live in the codebase. Always available, no - need for the database. -- MCP capability metadata lives in the database, so the knowledge "this - SearchSpace has these capabilities" survives any restart. -- The actual closures are built on demand from the database state. They - live in worker memory only until the worker dies, at which point they - get rebuilt by the next worker that needs them. - -### MCP database schema - -```sql -CREATE TABLE mcp_connections ( - id UUID PRIMARY KEY, - search_space_id INT REFERENCES search_spaces(id), - server_url TEXT, - transport TEXT, -- "http", "stdio", etc. - name TEXT, -- "Slack (Acme workspace)" - access_token BYTEA, -- encrypted at rest - refresh_token BYTEA, -- encrypted at rest - expires_at TIMESTAMPTZ, - last_harvested_at TIMESTAMPTZ, - created_at TIMESTAMPTZ, - created_by INT REFERENCES users(id) -); - -CREATE TABLE mcp_tools ( - id UUID PRIMARY KEY, - connection_id UUID REFERENCES mcp_connections(id) ON DELETE CASCADE, - name TEXT, -- "post_message" - description TEXT, - input_schema JSONB, - output_schema JSONB, - side_effects TEXT[], -- inferred or admin-curated - UNIQUE (connection_id, name) -); -``` - -### MCP lifecycle: connect, harvest, invoke - -Three phases, each with distinct concerns. - -**Phase 1 — Connect (one-time, on user action).** User clicks "Connect -Slack MCP." OAuth flow completes. A row is added to `mcp_connections` -with the encrypted tokens. - -**Phase 2 — Harvest (right after connect, also re-runnable).** SurfSense -opens a temporary client to the MCP server, calls `tools/list`, and writes -one row to `mcp_tools` per discovered tool. The temporary client is then -discarded; only the database state persists. - -```python -async def harvest_mcp_server(connection_id: UUID, ctx): - connection = await ctx.db.get(MCPConnection, connection_id) - client = build_temporary_client(connection) - tools = await client.list_tools() - - # Replace existing tool rows for this connection - await ctx.db.execute( - delete(MCPTool).where(MCPTool.connection_id == connection_id) - ) - for tool in tools: - ctx.db.add(MCPTool( - connection_id=connection_id, - name=tool.name, - description=tool.description, - input_schema=tool.inputSchema, - output_schema=tool.outputSchema, - side_effects=infer_side_effects(tool), - )) - connection.last_harvested_at = now() - await ctx.db.commit() -``` - -Harvesting can be re-run on a schedule (say, daily) or on user request, -to pick up new tools the server has added. - -**Phase 3 — Invoke (every time a step references an MCP capability).** -This is where the closure gets built. The executor calls -`ctx.get_capability("slack.post_message")`. The worker's in-memory cache is -checked; on miss, the database is queried: - -```python -async def get_capability(capability_id: str, ctx: ActionContext) -> Capability: - cached = _WORKER_CAPABILITY_CACHE.get((ctx.search_space.id, capability_id)) - if cached: - return cached - - if is_native(capability_id): - capability = _NATIVE_REGISTRY[capability_id] - else: - # MCP path: look up tool metadata - tool_row = await ctx.db.execute( - select(MCPTool) - .join(MCPConnection) - .where(MCPConnection.search_space_id == ctx.search_space.id) - .where(tool_qualified_name(MCPTool, MCPConnection) == capability_id) - ) - capability = Capability( - id=capability_id, - input_schema=tool_row.input_schema, - output_schema=tool_row.output_schema, - side_effects=set(tool_row.side_effects), - handler=make_mcp_handler( - connection_id=tool_row.connection_id, - tool_name=tool_row.name, - ), - ) - - _WORKER_CAPABILITY_CACHE[(ctx.search_space.id, capability_id)] = capability - return capability -``` - -The closure created by `make_mcp_handler` captures only the connection ID -and tool name. When invoked, it asks `ctx.resolve_mcp_client(connection_id)` -to build an authenticated client from the connection record (including -token refresh if needed). That client is also transient — built per call, -discarded after. +See archived design at `docs/automation/archived/mcp-registry.md` once +v1 ships; for now the only consumer of the registry is the in-memory +native path. ### Credentials: resolved at the moment of use From 144d702c354f4985aea68a9af06d35d58ecc8f3e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:35:37 +0200 Subject: [PATCH 019/133] docs(automation): defer credentials, cost, queue-routing, side-effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update §3 (Credentials), §7.1 (Dispatcher common path), §8 (Duration classes and queue routing), and §13 (Decisions locked) to reflect the v1-minimum scope: - Credentials block in §3 collapses to a deferred-to-Phase-2 note. The three guarantees (no creds in definition, no creds in LLM context, per-call resolution) return unchanged when Phase 2 ships external capabilities. - Cost-estimate pre-check in the dispatcher's common path is removed. Mid-flight budget kill in the executor still enforces budget_cap_usd. - Queue routing by expected_duration_seconds is deferred. Single automations_default queue in v1. - Decisions 24, 25, 26, 32-37, 38-41 marked deferred with explicit return phase. Three new v1-minimum decisions added (5-field Capability, measured-not-declared cost, single queue). All deferrals are additive: the original designs return as-is when warranted; nothing is rewritten between phases. --- automation-design-plan.md | 122 +++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 66 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index 07eca2049..9d738c6f7 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -138,47 +138,24 @@ See archived design at `docs/automation/archived/mcp-registry.md` once v1 ships; for now the only consumer of the registry is the in-memory native path. -### Credentials: resolved at the moment of use +### Credentials — deferred to Phase 2 -The handler doesn't carry credentials and the closure doesn't capture them. -When invoked, the handler asks `ActionContext` for what it needs: +The earlier per-call credential resolution pattern (`ctx.resolve_mcp_client`, +`ctx.resolve_http_client`, `ctx.resolve_llm`) is **deferred to Phase 2**. +v1 capabilities run server-side using app-level configuration; none of +the seven v1 capabilities needs per-user or per-connection auth. -```python -def make_mcp_handler(connection_id: UUID, tool_name: str): - async def handler(ctx: ActionContext, args: dict) -> Any: - # Credential resolution happens here, per call - client = await ctx.resolve_mcp_client(connection_id) - response = await client.call_tool(name=tool_name, arguments=args) - return response.content - return handler -``` +When Phase 2 ships external-credential capabilities (Slack, email, etc.), +the three guarantees the original design promised are reintroduced +unchanged: -`ctx.resolve_mcp_client(connection_id)`: -1. Loads the `mcp_connections` row -2. Decrypts the access token -3. Refreshes the token if it's expired (using the refresh token) -4. Constructs an `MCPClient` with the token set as a default authorization - header +- Credentials never appear in the automation definition (connection IDs + only). +- Credentials never appear in the LLM's context (the host holds them + and uses them on the LLM's behalf when executing tool calls). +- Credentials are loaded per-call, not pre-loaded into worker memory. -The HTTP library carries the auth header on every subsequent call the -client makes — the handler doesn't think about it after construction. - -For native capabilities calling external APIs directly, -`ctx.resolve_http_client(provider)` returns an authenticated `httpx` -client. For LLM operations, `ctx.resolve_llm(provider)` returns a -configured LLM client. **Three resolution methods, one pattern: the -context returns a client already authenticated.** - -Three properties this gives us: - -- **Credentials never appear in the automation definition.** The JSON - contains capability references and connection IDs, never tokens. -- **Credentials never appear in the LLM's context.** Even during - `agent_task`, the LLM sees tool descriptions only; the host holds - credentials and uses them when executing the tools the LLM requests. -- **Credentials are loaded per-call, not pre-loaded.** The credential - exists in memory only during the moment a handler is making a call. No - long-lived secrets in worker memory. +The Phase-2 design returns as-is; only the v1 surface is simplified. --- @@ -504,12 +481,18 @@ event, evaluates all matching triggers' filters, fires the matches. Common path (after a trigger has fired): 1. Resolve `inputs` from trigger payload and defaults 2. Validate resolved inputs against the automation's input schema -3. **Cost estimate** — sum capabilities' `cost_estimate(args)` for the plan; - refuse if exceeds `budget_cap_usd` -4. **Idempotency check** — dedup against existing pending/running runs -5. **Snapshot the resolved definition** into the run row (immutable history) -6. Enqueue executor task on the appropriate Celery queue (per - `expected_duration_seconds`) +3. **Idempotency check** — dedup against existing pending/running runs +4. **Snapshot the resolved definition** into the run row (immutable history) +5. Enqueue executor task on the single `automations_default` Celery queue + +The cost-estimate pre-check (originally step 3) is **deferred**. +v1 capabilities do not declare `cost_estimate`; pre-flight budgeting +returns when a historical-cost ledger exists. The mid-flight budget +cap (§7.2) still kills the run if accumulated cost crosses +`budget_cap_usd`. + +Queue routing by `expected_duration_seconds` is **deferred** until load +patterns justify a second queue. v1 uses a single queue. ### 7.2 Executor @@ -801,16 +784,18 @@ The engine handles storage (writes to SurfSense's existing object storage), URL generation (signed, scoped to the run's permissions), and cleanup (a nightly Celery Beat task deletes expired artifacts). -### Duration classes and queue routing +### Duration classes and queue routing — deferred -Capabilities declare `expected_duration_seconds`. The dispatcher routes -runs to Celery queues based on the longest-duration step: -- < 10s → `automations_fast` -- 10s – 5min → `automations_medium` -- 5min – 1hr → `automations_long` +The original design routed runs to multiple Celery queues based on each +capability's declared `expected_duration_seconds`. v1 ships with **one +queue** (`automations_default`) and capabilities do not declare a +duration. Multi-queue routing returns when burst load on a single queue +actually justifies the operational complexity of independent worker +pools. -Operators scale each queue's worker pool independently. A future "very -long" queue is a config change, not a contract change. +Adding the second queue is a config change plus reintroducing +`expected_duration_seconds` on the `Capability` dataclass — both +mechanical, additive, and free of design rewrite. --- @@ -1210,9 +1195,9 @@ place. ### Components 23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable -24. ✅ Side effects are a set, including `USER_VISIBLE` -25. ✅ `expected_duration_seconds` integer drives queue routing -26. ✅ `produces_artifacts` is a list of `ArtifactSpec`, not a bool +24. ⏸ Side effects are a set, including `USER_VISIBLE` — **deferred** until multi-user automation RBAC ships +25. ⏸ `expected_duration_seconds` integer drives queue routing — **deferred** until a second Celery queue is needed +26. ⏸ `produces_artifacts` is a list of `ArtifactSpec`, not a bool — **deferred** until artifacts beyond the deliverable handlers' own persistence are needed 27. ✅ Output schemas recommended on `agent_task`; editor warns when missing ### Event bus @@ -1220,20 +1205,25 @@ place. 29. ✅ Automations publish run events for composability 30. ✅ Publish/subscribe behind interface — no direct table access elsewhere -### Capability storage (two-tier persistence) +### Capability storage 31. ✅ Native capabilities registered in-memory at startup from the codebase. Identical across all workers. -32. ✅ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables. Survives restarts. -33. ✅ MCP handler closures built lazily per worker from database state. Worker-local cache, rebuilt on demand. -34. ✅ MCP server tool list re-harvested on a schedule (default: daily) and on user request. -35. ✅ MCP tools harvested into the capability registry at connection time -36. ✅ Side effects inferred from MCP hints + naming + admin overrides -37. ✅ MCP tools callable directly (no agent required) when caller knows args +32. ⏸ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables — **deferred to Phase 4** +33. ⏸ MCP handler closures built lazily per worker from database state — **deferred to Phase 4** +34. ⏸ MCP server tool list re-harvested on a schedule — **deferred to Phase 4** +35. ⏸ MCP tools harvested into the capability registry at connection time — **deferred to Phase 4** +36. ⏸ Side effects inferred from MCP hints + naming + admin overrides — **deferred to Phase 4** +37. ⏸ MCP tools callable directly (no agent required) when caller knows args — **deferred to Phase 4** -### Credentials -38. ✅ Credentials never appear in the automation definition — only connection IDs do -39. ✅ Credentials never appear in the LLM's context — the host holds them and uses them on the LLM's behalf -40. ✅ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment -41. ✅ Tokens encrypted at rest in the database; refresh handled automatically by `ActionContext.resolve_*_client` +### Credentials — all deferred to Phase 2 +38. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** +39. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** +40. ⏸ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment — **Phase 2** +41. ⏸ Tokens encrypted at rest; refresh handled automatically by `ActionContext.resolve_*_client` — **Phase 2** + +### v1-minimum (new lock) +v1. ✅ `Capability` is exactly five fields: `id`, `description`, `input_schema`, `output_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. +v2. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. +v3. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. ### NL authoring 42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary From db8c472664b301d38c75d30d96bf65136a11a981 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:37:05 +0200 Subject: [PATCH 020/133] docs(automation): narrow v1 data model + Phase 1 scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §9 (Data model): drop from six tables to three. v1 ships automations, automation_triggers, automation_runs only. domain_events deferred to Phase 3 (event trigger); mcp_connections/mcp_tools deferred to Phase 4 (MCP integration). Remove the table definitions for the deferred ones and replace with a deferred-tables note pointing to the consuming phase. automation_triggers.type enum narrowed to schedule|manual for v1. Webhook and event types ship with their respective phases. secret_hash column deferred to Phase 2 alongside the webhook trigger. automation_runs.cost_usd column deferred until at least one v1 capability records token-level cost — additive when reintroduced. §14 (Phase 1) reorganized into four explicit steps matching the work we're about to do: scaffolding + schemas + empty registries (step 1), then registry population (step 2), then executor (step 3), then NL authoring + UI (step 4). The current commit batch lands step 1 only. --- automation-design-plan.md | 126 ++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index 9d738c6f7..f57385e31 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -801,12 +801,20 @@ mechanical, additive, and free of design rewrite. ## 9. Data model -Six tables. All scoped by `search_space_id` for RBAC. +**v1 ships three tables:** `automations`, `automation_triggers`, +`automation_runs`. All scoped by `search_space_id` for RBAC. -The first four (`automations`, `automation_triggers`, `automation_runs`, -`domain_events`) are the engine's own state. The last two -(`mcp_connections`, `mcp_tools`) hold the durable knowledge that backs -MCP-derived capabilities — see §3 for the lifecycle rationale. +The other three tables described in earlier drafts are deferred: + +- `domain_events` → **deferred to Phase 3** (introduced with the event + trigger). +- `mcp_connections`, `mcp_tools` → **deferred to Phase 4** (MCP + integration). + +The deferred tables ship as-is when their consuming feature lands; +nothing in the v1 schema needs to change to accommodate them. The three +v1 tables form the engine's persistent state — definitions, triggers, +and an immutable run history. ### `automations` @@ -828,12 +836,14 @@ MCP-derived capabilities — see §3 for the lifecycle rationale. | --------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | | `id` | int PK | | | `automation_id` | FK | | -| `type` | enum: `schedule`, `webhook`, `event` | | +| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | | `config` | jsonb | validated against trigger's `config_schema` | | `enabled` | bool | | -| `secret_hash` | str / null | for webhook bearer tokens | | `last_fired_at` | timestamp | | +`secret_hash` (for webhook bearer tokens) is **deferred to Phase 2** with +the webhook trigger. + ### `automation_runs` | field | type | notes | @@ -849,61 +859,25 @@ MCP-derived capabilities — see §3 for the lifecycle rationale. | `output` | jsonb / null | | | `artifacts` | jsonb | references to created artifacts | | `error` | jsonb / null | | -| `cost_usd` | decimal | accumulated cost | | `started_at` / `finished_at` | timestamps | | | `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | -### `domain_events` +`cost_usd` (per-run accumulated cost) is **deferred** until at least one +v1 capability records token-level cost. When reintroduced it lands as a +column-only migration. -| field | type | notes | -| ----------------- | ----------- | -------------------------------------------------- | -| `id` | UUID PK | | -| `search_space_id` | FK | scoping | -| `event_type` | varchar | e.g. `drive.file_added`, `automation.run.succeeded` | -| `source_id` | varchar | which connector/automation/etc. produced it | -| `payload` | jsonb | matches the event type's documented schema | -| `created_at` | timestamp | | -| `consumed_by` | jsonb | array of consumer_ids, for tracking + replay | -| `expires_at` | timestamp | auto-cleanup after 7 days | +### Deferred tables -### `mcp_connections` +- **`domain_events`** — the event bus backing event triggers. Ships in + Phase 3 with the event trigger. v1 only emits `automation.run.*` + events into application logs; the table is added when at least one + consumer needs to subscribe to them. +- **`mcp_connections`** / **`mcp_tools`** — see §3. Both ship in Phase 4 + alongside the MCP harvester and the two-tier registry. -Persistent record of MCP server connections per SearchSpace. - -| field | type | notes | -| ------------------- | ----------- | -------------------------------------------------- | -| `id` | UUID PK | | -| `search_space_id` | FK | scoping | -| `server_url` | text | the MCP server's endpoint | -| `transport` | text | `"http"`, `"stdio"`, etc. | -| `name` | text | human-readable label (e.g., "Slack — Acme") | -| `access_token` | bytea | encrypted at rest | -| `refresh_token` | bytea | encrypted at rest | -| `expires_at` | timestamp | for OAuth tokens | -| `last_harvested_at` | timestamp | when tool list was last refreshed | -| `created_at` | timestamp | | -| `created_by` | FK → users | | - -### `mcp_tools` - -The tool list each connected MCP server exposes. Acts as the durable -source for MCP capabilities — definitions reference `mcp_tools` rows by -qualified name, and worker processes lazily build handler closures from -this state. - -| field | type | notes | -| --------------- | ----------- | ------------------------------------------------ | -| `id` | UUID PK | | -| `connection_id` | FK → `mcp_connections.id` ON DELETE CASCADE | | -| `name` | text | the tool name reported by the MCP server | -| `description` | text | description for the NL generator and form editor | -| `input_schema` | jsonb | JSON Schema for tool arguments | -| `output_schema` | jsonb | JSON Schema for tool results | -| `side_effects` | text[] | inferred from MCP hints + naming + admin override | -| UNIQUE | | (connection_id, name) | - -NL drafts are **not** a core table. They live in a generic short-TTL store -(Redis or a transient table) when the NL flow is built in Phase 3. +NL drafts are **not** a core table. They live in a generic short-TTL +store (Redis or a transient table) when the NL flow is built in +Phase 3. --- @@ -1092,21 +1066,39 @@ which actions and triggers are available, not whether users can describe automations in natural language. ### Phase 1 — Engine MVP with NL authoring -- 4 tables + Alembic migration -- Capability registry with native capabilities (`search_space.query`, - `search_space.fetch_document`, `agent.run`) -- `agent_task` action only -- `schedule` trigger + manual "Run now" endpoint -- Executor with retries, timeouts, budget caps -- Template engine (Jinja sandbox + 15 filters + 4 runtime limits) -- **NL authoring flow**: Generator LLM, deterministic validator, - Review LLM, editable form + +**Step 1 (current scope, this batch of commits):** +- 3 tables (`automations`, `automation_triggers`, `automation_runs`) + + Alembic migration +- Empty Capability, Action, Trigger registries (concrete entries land in + later steps when the consuming feature lands) +- Pydantic schemas for the automation definition envelope, the two v1 + trigger configs (`schedule`, `manual`), and the one v1 action config + (`agent_task`) +- Module structure under `app/automations/` (data/, schemas/, + registries/), fully isolated from the existing codebase + +**Step 2:** +- Register the `agent_task` action and the `schedule` / `manual` + triggers in the registries +- Capability registry populated with native deliverable-producing + capabilities (chosen when this step starts) + +**Step 3:** +- Executor (single-queue Celery task) with retries, timeouts, budget + caps measured against `cost_usd` ledger on the run +- Template engine (Jinja sandbox + the v1 filter allowlist + runtime + limits) +- Manual "Run now" endpoint + +**Step 4:** +- NL authoring flow: Generator LLM, deterministic validator, Review LLM, + editable form - Run history UI with Electric SQL streaming **After Phase 1**: a user can describe an automation in natural language, review the proposal (with summary + flagged anomalies), edit any field, -save, and watch it run on a schedule. The Claude Routines value -proposition, on SurfSense's data, with NL-first authoring. +save, and watch it run on a schedule. ### Phase 2 — Webhooks and delivery - `webhook` trigger with per-automation bearer tokens From 113748dfd5054aba1d3e16f7d7350297de0990ca Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:38:41 +0200 Subject: [PATCH 021/133] feat(automation): scaffold isolated module structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create app/automations/ with the SRP-per-file / grouped-folders layout that mirrors app/agents/multi_agent_chat/. Twelve __init__.py files, each a thin re-export with a single-line docstring describing the subpackage's role, no exports yet (filled in subsequent commits). Tree: app/automations/ ├── persistence/ │ ├── enums/ (status / type enums; one per file) │ └── models/ (SQLAlchemy tables; one per file) ├── schemas/ │ ├── definition/ (the JSON envelope, broken by concern) │ ├── triggers/ (per-trigger config schemas) │ └── actions/ (per-action config schemas) └── registries/ ├── capabilities/ (types.py + store.py) ├── actions/ (types.py + store.py) └── triggers/ (types.py + store.py) The persistence/ folder is named to avoid surfsense_backend/.gitignore's data/ ignore rule, which silently masked the original data/ name and its contents from version control. Isolation invariant: the module imports only from app.db (foundational Base + FK targets, unavoidable) and stdlib / SQLAlchemy / Pydantic. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.* or any other business-logic module. Confirmed importable with no side effects. --- surfsense_backend/app/automations/__init__.py | 5 +++++ surfsense_backend/app/automations/persistence/__init__.py | 5 +++++ .../app/automations/persistence/enums/__init__.py | 5 +++++ .../app/automations/persistence/models/__init__.py | 5 +++++ surfsense_backend/app/automations/registries/__init__.py | 5 +++++ .../app/automations/registries/actions/__init__.py | 5 +++++ .../app/automations/registries/capabilities/__init__.py | 5 +++++ .../app/automations/registries/triggers/__init__.py | 5 +++++ surfsense_backend/app/automations/schemas/__init__.py | 5 +++++ .../app/automations/schemas/actions/__init__.py | 5 +++++ .../app/automations/schemas/definition/__init__.py | 5 +++++ .../app/automations/schemas/triggers/__init__.py | 5 +++++ 12 files changed, 60 insertions(+) create mode 100644 surfsense_backend/app/automations/__init__.py create mode 100644 surfsense_backend/app/automations/persistence/__init__.py create mode 100644 surfsense_backend/app/automations/persistence/enums/__init__.py create mode 100644 surfsense_backend/app/automations/persistence/models/__init__.py create mode 100644 surfsense_backend/app/automations/registries/__init__.py create mode 100644 surfsense_backend/app/automations/registries/actions/__init__.py create mode 100644 surfsense_backend/app/automations/registries/capabilities/__init__.py create mode 100644 surfsense_backend/app/automations/registries/triggers/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/actions/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/definition/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/triggers/__init__.py diff --git a/surfsense_backend/app/automations/__init__.py b/surfsense_backend/app/automations/__init__.py new file mode 100644 index 000000000..edb7891ea --- /dev/null +++ b/surfsense_backend/app/automations/__init__.py @@ -0,0 +1,5 @@ +"""Automations: scheduled / triggered runs of capabilities — see automation-design-plan.md.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py new file mode 100644 index 000000000..05c39014e --- /dev/null +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -0,0 +1,5 @@ +"""Persistence layer: SQLAlchemy enums under ``enums/`` and models under ``models/``.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/persistence/enums/__init__.py b/surfsense_backend/app/automations/persistence/enums/__init__.py new file mode 100644 index 000000000..f221687dc --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/__init__.py @@ -0,0 +1,5 @@ +"""SQLAlchemy / Python enums backing the three automation tables.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py new file mode 100644 index 000000000..da73c9e41 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -0,0 +1,5 @@ +"""SQLAlchemy models: one file per table (``automation.py``, ``trigger.py``, ``run.py``).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py new file mode 100644 index 000000000..e7334cca8 --- /dev/null +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -0,0 +1,5 @@ +"""Three registries — ``capabilities/``, ``actions/``, ``triggers/`` — populated at import time.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py new file mode 100644 index 000000000..6b19b7091 --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -0,0 +1,5 @@ +"""Action registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py new file mode 100644 index 000000000..77f9f88b7 --- /dev/null +++ b/surfsense_backend/app/automations/registries/capabilities/__init__.py @@ -0,0 +1,5 @@ +"""Capability registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py new file mode 100644 index 000000000..bc795b61a --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -0,0 +1,5 @@ +"""Trigger registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py new file mode 100644 index 000000000..67211b898 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -0,0 +1,5 @@ +"""Pydantic schemas: definition envelope, trigger configs, action configs.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py new file mode 100644 index 000000000..1aa68b629 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -0,0 +1,5 @@ +"""Per-action config schemas: one file per action type registered in v1.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py new file mode 100644 index 000000000..3fbda8cc8 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -0,0 +1,5 @@ +"""Automation definition envelope: the editable structured spec users author and run.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py new file mode 100644 index 000000000..2da765bc3 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -0,0 +1,5 @@ +"""Per-trigger config schemas: one file per trigger type registered in v1.""" + +from __future__ import annotations + +__all__: list[str] = [] From 05931375f4c5d92d5737c3c06bef718a1712d374 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:42:50 +0200 Subject: [PATCH 022/133] feat(automation): add SQLAlchemy models for the three v1 tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three enums (one file each) plus three models (one file each), all under app/automations/persistence/. The module imports from app.db only (Base/BaseModel/TimestampMixin and FK targets searchspaces.id / user.id); no business-logic imports. Enums: - AutomationStatus: active | paused | archived - RunStatus: pending | running | succeeded | failed | cancelled | timed_out - TriggerType: schedule | manual (Phase-2/3 add webhook | event) Models: - Automation: search_space-scoped, created_by_user_id (SET NULL), name + description, status enum, definition JSONB, version int, updated_at with onupdate. - AutomationTrigger: FK → automations (CASCADE), type enum, config JSONB, enabled bool, last_fired_at. Webhook secret_hash is omitted until Phase 2. - AutomationRun: FK → automations (CASCADE), nullable trigger_id (SET NULL — null = manual via UI), status enum, definition_snapshot for immutable history, trigger_payload / resolved_inputs / step_results / output / artifacts / error JSONB columns, started_at / finished_at timestamps, agent_session_id for linking to the LangGraph trace. cost_usd column omitted until at least one v1 capability records token-level cost. Verified: Base.metadata exposes all three table names; columns and enums introspect as documented; no linter errors. --- .../app/automations/persistence/__init__.py | 12 ++- .../automations/persistence/enums/__init__.py | 10 ++- .../persistence/enums/automation_status.py | 18 +++++ .../persistence/enums/run_status.py | 28 +++++++ .../persistence/enums/trigger_type.py | 21 ++++++ .../persistence/models/__init__.py | 10 ++- .../persistence/models/automation.py | 75 +++++++++++++++++++ .../app/automations/persistence/models/run.py | 72 ++++++++++++++++++ .../automations/persistence/models/trigger.py | 57 ++++++++++++++ 9 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 surfsense_backend/app/automations/persistence/enums/automation_status.py create mode 100644 surfsense_backend/app/automations/persistence/enums/run_status.py create mode 100644 surfsense_backend/app/automations/persistence/enums/trigger_type.py create mode 100644 surfsense_backend/app/automations/persistence/models/automation.py create mode 100644 surfsense_backend/app/automations/persistence/models/run.py create mode 100644 surfsense_backend/app/automations/persistence/models/trigger.py diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py index 05c39014e..265742a85 100644 --- a/surfsense_backend/app/automations/persistence/__init__.py +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -2,4 +2,14 @@ from __future__ import annotations -__all__: list[str] = [] +from .enums import AutomationStatus, RunStatus, TriggerType +from .models import Automation, AutomationRun, AutomationTrigger + +__all__ = [ + "Automation", + "AutomationRun", + "AutomationStatus", + "AutomationTrigger", + "RunStatus", + "TriggerType", +] diff --git a/surfsense_backend/app/automations/persistence/enums/__init__.py b/surfsense_backend/app/automations/persistence/enums/__init__.py index f221687dc..cf9e7dd1b 100644 --- a/surfsense_backend/app/automations/persistence/enums/__init__.py +++ b/surfsense_backend/app/automations/persistence/enums/__init__.py @@ -2,4 +2,12 @@ from __future__ import annotations -__all__: list[str] = [] +from .automation_status import AutomationStatus +from .run_status import RunStatus +from .trigger_type import TriggerType + +__all__ = [ + "AutomationStatus", + "RunStatus", + "TriggerType", +] diff --git a/surfsense_backend/app/automations/persistence/enums/automation_status.py b/surfsense_backend/app/automations/persistence/enums/automation_status.py new file mode 100644 index 000000000..3f2ca9621 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/automation_status.py @@ -0,0 +1,18 @@ +"""``AutomationStatus`` — lifecycle of a stored automation definition.""" + +from __future__ import annotations + +from enum import StrEnum + + +class AutomationStatus(StrEnum): + """Status of an automation in the registry. + + ``active`` — eligible to fire from its triggers. + ``paused`` — definition retained, triggers do not fire. + ``archived`` — kept for run history only; no edits, no fires. + """ + + ACTIVE = "active" + PAUSED = "paused" + ARCHIVED = "archived" diff --git a/surfsense_backend/app/automations/persistence/enums/run_status.py b/surfsense_backend/app/automations/persistence/enums/run_status.py new file mode 100644 index 000000000..0f619bd82 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/run_status.py @@ -0,0 +1,28 @@ +"""``RunStatus`` — the state machine of a single ``AutomationRun``.""" + +from __future__ import annotations + +from enum import StrEnum + + +class RunStatus(StrEnum): + """Lifecycle states of an ``AutomationRun`` row. + + Transitions are linear with three terminal branches: + + pending → running → (succeeded | failed | cancelled | timed_out) + + ``pending`` — row created, executor task enqueued, work not started. + ``running`` — executor has picked up the run. + ``succeeded`` — terminal: plan completed without error. + ``failed`` — terminal: at least one step raised an unrecoverable error. + ``cancelled`` — terminal: caller asked for cancellation. + ``timed_out`` — terminal: run exceeded its configured timeout. + """ + + PENDING = "pending" + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + CANCELLED = "cancelled" + TIMED_OUT = "timed_out" diff --git a/surfsense_backend/app/automations/persistence/enums/trigger_type.py b/surfsense_backend/app/automations/persistence/enums/trigger_type.py new file mode 100644 index 000000000..eb06fe773 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/trigger_type.py @@ -0,0 +1,21 @@ +"""``TriggerType`` — the trigger-kind discriminator (v1 = schedule, manual).""" + +from __future__ import annotations + +from enum import StrEnum + + +class TriggerType(StrEnum): + """Kind of trigger an ``AutomationTrigger`` row represents. + + v1 ships two kinds: + + ``schedule`` — fires on a cron expression managed by Celery Beat. + ``manual`` — fires on demand from the UI's "Run now" affordance. + + ``webhook`` and ``event`` are deferred to Phase 2 and Phase 3 + respectively; adding them is an enum-value extension only. + """ + + SCHEDULE = "schedule" + MANUAL = "manual" diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py index da73c9e41..4aca02a03 100644 --- a/surfsense_backend/app/automations/persistence/models/__init__.py +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -2,4 +2,12 @@ from __future__ import annotations -__all__: list[str] = [] +from .automation import Automation +from .run import AutomationRun +from .trigger import AutomationTrigger + +__all__ = [ + "Automation", + "AutomationRun", + "AutomationTrigger", +] diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py new file mode 100644 index 000000000..fc4a1ed93 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -0,0 +1,75 @@ +"""``Automation`` table — the editable, versioned automation definition.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +from sqlalchemy import ( + TIMESTAMP, + Column, + Enum as SQLAlchemyEnum, + ForeignKey, + Integer, + String, + Text, +) +from sqlalchemy.dialects.postgresql import JSONB, UUID + +from app.db import BaseModel, TimestampMixin + +from ..enums.automation_status import AutomationStatus + + +class Automation(BaseModel, TimestampMixin): + """The editable, versioned spec a user authors. + + The ``definition`` JSON is what the user (or the NL generator) writes + and edits. Each save bumps ``version`` by one; the previous JSON is + not kept in this row — version history is reconstructed from the + ``definition_snapshot`` column on every ``AutomationRun`` that fired + against a given version. + """ + + __tablename__ = "automations" + + search_space_id = Column( + Integer, + ForeignKey("searchspaces.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + created_by_user_id = Column( + UUID(as_uuid=True), + ForeignKey("user.id", ondelete="SET NULL"), + nullable=True, + index=True, + ) + + name = Column(String(200), nullable=False) + description = Column(Text, nullable=True) + + status = Column( + SQLAlchemyEnum(AutomationStatus, name="automation_status"), + nullable=False, + default=AutomationStatus.ACTIVE, + server_default=AutomationStatus.ACTIVE.value, + index=True, + ) + + definition = Column(JSONB, nullable=False) + + version = Column( + Integer, + nullable=False, + default=1, + server_default="1", + ) + + updated_at = Column( + TIMESTAMP(timezone=True), + nullable=False, + default=lambda: datetime.now(UTC), + onupdate=lambda: datetime.now(UTC), + index=True, + ) diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py new file mode 100644 index 000000000..5c6ec93ec --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -0,0 +1,72 @@ +"""``AutomationRun`` table — the immutable per-fire execution record.""" + +from __future__ import annotations + +from sqlalchemy import ( + TIMESTAMP, + Column, + Enum as SQLAlchemyEnum, + ForeignKey, + Integer, + String, +) +from sqlalchemy.dialects.postgresql import JSONB + +from app.db import BaseModel, TimestampMixin + +from ..enums.run_status import RunStatus + + +class AutomationRun(BaseModel, TimestampMixin): + """One execution of an automation. + + Every fire of any trigger inserts exactly one row here. The row is + immutable from the user's perspective — the executor only updates + ``status``, ``step_results``, ``output``, ``artifacts``, ``error``, + ``started_at``, ``finished_at`` as the run progresses; the + ``definition_snapshot`` is locked at fire time so the user can always + see exactly what code path executed for any historical run. + """ + + __tablename__ = "automation_runs" + + automation_id = Column( + Integer, + ForeignKey("automations.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + trigger_id = Column( + Integer, + ForeignKey("automation_triggers.id", ondelete="SET NULL"), + nullable=True, + index=True, + ) + + status = Column( + SQLAlchemyEnum(RunStatus, name="automation_run_status"), + nullable=False, + default=RunStatus.PENDING, + server_default=RunStatus.PENDING.value, + index=True, + ) + + definition_snapshot = Column(JSONB, nullable=False) + + trigger_payload = Column(JSONB, nullable=True) + + resolved_inputs = Column(JSONB, nullable=False, server_default="{}") + + step_results = Column(JSONB, nullable=False, server_default="[]") + + output = Column(JSONB, nullable=True) + + artifacts = Column(JSONB, nullable=False, server_default="[]") + + error = Column(JSONB, nullable=True) + + started_at = Column(TIMESTAMP(timezone=True), nullable=True) + finished_at = Column(TIMESTAMP(timezone=True), nullable=True) + + agent_session_id = Column(String(200), nullable=True) diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py new file mode 100644 index 000000000..3173770d6 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -0,0 +1,57 @@ +"""``AutomationTrigger`` table — one row per (automation, trigger-instance) pair.""" + +from __future__ import annotations + +from sqlalchemy import ( + TIMESTAMP, + Boolean, + Column, + Enum as SQLAlchemyEnum, + ForeignKey, + Integer, +) +from sqlalchemy.dialects.postgresql import JSONB + +from app.db import BaseModel, TimestampMixin + +from ..enums.trigger_type import TriggerType + + +class AutomationTrigger(BaseModel, TimestampMixin): + """One trigger attached to an automation. + + An automation may have multiple triggers — e.g. a ``schedule`` trigger + for the autonomous path and a ``manual`` trigger backing the UI's + "Run now" affordance. Each trigger's ``config`` is validated against + the registered ``TriggerDefinition.config_schema`` for its ``type``. + """ + + __tablename__ = "automation_triggers" + + automation_id = Column( + Integer, + ForeignKey("automations.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + type = Column( + SQLAlchemyEnum(TriggerType, name="automation_trigger_type"), + nullable=False, + index=True, + ) + + config = Column(JSONB, nullable=False) + + enabled = Column( + Boolean, + nullable=False, + default=True, + server_default="true", + index=True, + ) + + last_fired_at = Column( + TIMESTAMP(timezone=True), + nullable=True, + ) From d9183464d9ec5e69f4630083993b0e3277eda7d4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:44:33 +0200 Subject: [PATCH 023/133] feat(automation): add Alembic migration for the three automation tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 144 -> 143. Matches the SQLAlchemy models added in commit 7 and the v1 data model in automation-design-plan.md §9. Up: - CREATE TYPE automation_status / automation_trigger_type / automation_run_status (PostgreSQL ENUMs created first because the tables reference them). - CREATE TABLE automations with FK to searchspaces (CASCADE) and user (SET NULL); five indexes matching the SQLAlchemy model. - CREATE TABLE automation_triggers with FK to automations (CASCADE); four indexes. - CREATE TABLE automation_runs with FK to automations (CASCADE) and automation_triggers (SET NULL — null trigger_id == manual via UI); four indexes. Down: drops every index, table, and ENUM in reverse-dependency order so the migration is reversible without ON DELETE side effects. Verified: `alembic history` resolves 143 -> 144 (head) cleanly. domain_events (Phase 3) and mcp_connections / mcp_tools (Phase 4) ship in their own migrations when the consuming feature lands; this migration only covers the three v1 tables. --- .../versions/144_add_automation_tables.py | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 surfsense_backend/alembic/versions/144_add_automation_tables.py diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py new file mode 100644 index 000000000..6aa208dc1 --- /dev/null +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -0,0 +1,167 @@ +"""Add automation tables (automations, automation_triggers, automation_runs) + +Revision ID: 144 +Revises: 143 +Create Date: 2026-05-26 + +Adds the three tables that back the v1 automation engine, plus the +three PostgreSQL ENUM types they reference. Matches the SQLAlchemy +models under ``app.automations.persistence.models`` and the v1 data +model in ``automation-design-plan.md`` §9. + +v1 ships these three tables only. ``domain_events`` is deferred to +Phase 3 with the event trigger; ``mcp_connections`` / ``mcp_tools`` +are deferred to Phase 4 with the MCP integration. +""" + +from collections.abc import Sequence + +from alembic import op + +revision: str = "144" +down_revision: str | None = "143" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ENUM types (PostgreSQL requires types created before tables that use them) + op.execute( + """ + CREATE TYPE automation_status AS ENUM ( + 'active', 'paused', 'archived' + ); + """ + ) + op.execute( + """ + CREATE TYPE automation_trigger_type AS ENUM ( + 'schedule', 'manual' + ); + """ + ) + op.execute( + """ + CREATE TYPE automation_run_status AS ENUM ( + 'pending', 'running', 'succeeded', 'failed', + 'cancelled', 'timed_out' + ); + """ + ) + + # automations — the editable, versioned automation definition + op.execute( + """ + CREATE TABLE automations ( + id SERIAL PRIMARY KEY, + search_space_id INTEGER NOT NULL + REFERENCES searchspaces(id) ON DELETE CASCADE, + created_by_user_id UUID + REFERENCES "user"(id) ON DELETE SET NULL, + name VARCHAR(200) NOT NULL, + description TEXT, + status automation_status NOT NULL DEFAULT 'active', + definition JSONB NOT NULL, + version INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + """ + ) + op.execute( + "CREATE INDEX ix_automations_search_space_id ON automations(search_space_id);" + ) + op.execute( + "CREATE INDEX ix_automations_created_by_user_id ON automations(created_by_user_id);" + ) + op.execute("CREATE INDEX ix_automations_status ON automations(status);") + op.execute("CREATE INDEX ix_automations_created_at ON automations(created_at);") + op.execute("CREATE INDEX ix_automations_updated_at ON automations(updated_at);") + + # automation_triggers — one row per (automation, trigger-instance) pair + op.execute( + """ + CREATE TABLE automation_triggers ( + id SERIAL PRIMARY KEY, + automation_id INTEGER NOT NULL + REFERENCES automations(id) ON DELETE CASCADE, + type automation_trigger_type NOT NULL, + config JSONB NOT NULL, + enabled BOOLEAN NOT NULL DEFAULT true, + last_fired_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + """ + ) + op.execute( + "CREATE INDEX ix_automation_triggers_automation_id ON automation_triggers(automation_id);" + ) + op.execute( + "CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);" + ) + op.execute( + "CREATE INDEX ix_automation_triggers_enabled ON automation_triggers(enabled);" + ) + op.execute( + "CREATE INDEX ix_automation_triggers_created_at ON automation_triggers(created_at);" + ) + + # automation_runs — the immutable per-fire execution record + op.execute( + """ + CREATE TABLE automation_runs ( + id SERIAL PRIMARY KEY, + automation_id INTEGER NOT NULL + REFERENCES automations(id) ON DELETE CASCADE, + trigger_id INTEGER + REFERENCES automation_triggers(id) ON DELETE SET NULL, + status automation_run_status NOT NULL DEFAULT 'pending', + definition_snapshot JSONB NOT NULL, + trigger_payload JSONB, + resolved_inputs JSONB NOT NULL DEFAULT '{}'::jsonb, + step_results JSONB NOT NULL DEFAULT '[]'::jsonb, + output JSONB, + artifacts JSONB NOT NULL DEFAULT '[]'::jsonb, + error JSONB, + started_at TIMESTAMP WITH TIME ZONE, + finished_at TIMESTAMP WITH TIME ZONE, + agent_session_id VARCHAR(200), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + """ + ) + op.execute( + "CREATE INDEX ix_automation_runs_automation_id ON automation_runs(automation_id);" + ) + op.execute( + "CREATE INDEX ix_automation_runs_trigger_id ON automation_runs(trigger_id);" + ) + op.execute("CREATE INDEX ix_automation_runs_status ON automation_runs(status);") + op.execute( + "CREATE INDEX ix_automation_runs_created_at ON automation_runs(created_at);" + ) + + +def downgrade() -> None: + op.execute("DROP INDEX IF EXISTS ix_automation_runs_created_at;") + op.execute("DROP INDEX IF EXISTS ix_automation_runs_status;") + op.execute("DROP INDEX IF EXISTS ix_automation_runs_trigger_id;") + op.execute("DROP INDEX IF EXISTS ix_automation_runs_automation_id;") + op.execute("DROP TABLE IF EXISTS automation_runs;") + + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_created_at;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_enabled;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_type;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_automation_id;") + op.execute("DROP TABLE IF EXISTS automation_triggers;") + + op.execute("DROP INDEX IF EXISTS ix_automations_updated_at;") + op.execute("DROP INDEX IF EXISTS ix_automations_created_at;") + op.execute("DROP INDEX IF EXISTS ix_automations_status;") + op.execute("DROP INDEX IF EXISTS ix_automations_created_by_user_id;") + op.execute("DROP INDEX IF EXISTS ix_automations_search_space_id;") + op.execute("DROP TABLE IF EXISTS automations;") + + op.execute("DROP TYPE IF EXISTS automation_run_status;") + op.execute("DROP TYPE IF EXISTS automation_trigger_type;") + op.execute("DROP TYPE IF EXISTS automation_status;") From be4d43d6c9c41ea115c7a6e4a2bbf41afbf241b3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:50:52 +0200 Subject: [PATCH 024/133] feat(automation): add Pydantic schemas for the automation definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three layers of Pydantic models under app/automations/schemas/, one file per concern (SRP), matching the envelope in automation-design-plan.md §5. definition/ — the editable envelope persisted in automations.definition: - envelope.py AutomationDefinition (top-level shape) - plan_step.py PlanStep (one step in the sequential plan) - inputs.py InputsBlock (the inputs JSON Schema wrapper) - execution.py ExecutionBlock (timeouts, retries, concurrency, budget cap, on_failure plan) - metadata.py MetadataBlock (tags + created_from_nl + extras) - trigger_spec.py TriggerSpec (one entry in triggers[]) triggers/ — per-trigger config schemas, dispatched by registry on the TriggerSpec.type discriminator: - schedule.py ScheduleTriggerConfig(cron, timezone) - manual.py ManualTriggerConfig() — empty in v1 actions/ — per-action config schemas, dispatched by registry on the PlanStep.action discriminator: - agent_task.py AgentTaskActionConfig(prompt, tools, model, output_schema) Design properties verified by an inline smoke test: - The §5 worked example round-trips through model_validate_json / model_dump_json byte-for-byte (InputsBlock uses serialize_by_alias so the JSON key stays "schema" not "schema_"). - Envelope rejects unknown top-level keys (extra="forbid"). - MetadataBlock tolerates unknown keys (extra="allow"). - ExecutionBlock defaults apply when the block is omitted. - retry_backoff and concurrency are typed as Literal — bogus values rejected at validation time. - Per-type configs enforce their required fields (cron + timezone on schedule; non-empty prompt on agent_task). The envelope keeps trigger and action configs as untyped dicts on purpose — per-type validation is a registry-driven dispatch (commit 10), keeping the envelope free of every-type-knows-every-type coupling. --- .../app/automations/schemas/__init__.py | 23 ++++- .../automations/schemas/actions/__init__.py | 6 +- .../automations/schemas/actions/agent_task.py | 66 ++++++++++++++ .../schemas/definition/__init__.py | 16 +++- .../schemas/definition/envelope.py | 89 +++++++++++++++++++ .../schemas/definition/execution.py | 76 ++++++++++++++++ .../automations/schemas/definition/inputs.py | 43 +++++++++ .../schemas/definition/metadata.py | 36 ++++++++ .../schemas/definition/plan_step.py | 86 ++++++++++++++++++ .../schemas/definition/trigger_spec.py | 40 +++++++++ .../automations/schemas/triggers/__init__.py | 8 +- .../automations/schemas/triggers/manual.py | 21 +++++ .../automations/schemas/triggers/schedule.py | 33 +++++++ 13 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 surfsense_backend/app/automations/schemas/actions/agent_task.py create mode 100644 surfsense_backend/app/automations/schemas/definition/envelope.py create mode 100644 surfsense_backend/app/automations/schemas/definition/execution.py create mode 100644 surfsense_backend/app/automations/schemas/definition/inputs.py create mode 100644 surfsense_backend/app/automations/schemas/definition/metadata.py create mode 100644 surfsense_backend/app/automations/schemas/definition/plan_step.py create mode 100644 surfsense_backend/app/automations/schemas/definition/trigger_spec.py create mode 100644 surfsense_backend/app/automations/schemas/triggers/manual.py create mode 100644 surfsense_backend/app/automations/schemas/triggers/schedule.py diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 67211b898..83a95a2a8 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -2,4 +2,25 @@ from __future__ import annotations -__all__: list[str] = [] +from .actions import AgentTaskActionConfig +from .definition import ( + AutomationDefinition, + ExecutionBlock, + InputsBlock, + MetadataBlock, + PlanStep, + TriggerSpec, +) +from .triggers import ManualTriggerConfig, ScheduleTriggerConfig + +__all__ = [ + "AgentTaskActionConfig", + "AutomationDefinition", + "ExecutionBlock", + "InputsBlock", + "ManualTriggerConfig", + "MetadataBlock", + "PlanStep", + "ScheduleTriggerConfig", + "TriggerSpec", +] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py index 1aa68b629..17c257562 100644 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -2,4 +2,8 @@ from __future__ import annotations -__all__: list[str] = [] +from .agent_task import AgentTaskActionConfig + +__all__ = [ + "AgentTaskActionConfig", +] diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py new file mode 100644 index 000000000..74e41166a --- /dev/null +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -0,0 +1,66 @@ +"""``AgentTaskActionConfig`` — config for the ``agent_task`` action type.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class AgentTaskActionConfig(BaseModel): + """Config for an ``agent_task`` plan step. + + Validated against ``PlanStep.config`` whenever the step's + ``action`` is ``agent_task``. The step instructs the LangGraph + Deep Agent runtime to: + + 1. Receive ``prompt`` (with all preceding-step outputs and inputs + already rendered by the template engine). + 2. Run the agent with access to *exactly* the capabilities named + in ``tools`` — nothing else from the registry is visible to + this agent invocation. + 3. Return a JSON object matching ``output_schema`` (recommended; + the executor validates and re-prompts on mismatch). + + ``output_schema`` is the design's "dynamic output contract" — + instead of locking the output shape on the ActionDefinition (as + tight actions do), the user declares the shape they want for this + specific step, and the agent has to match it. + """ + + model_config = ConfigDict(extra="forbid") + + prompt: str = Field( + ..., + description=( + "The task prompt rendered through the Jinja sandbox. May " + "reference automation inputs and prior-step outputs." + ), + min_length=1, + ) + tools: list[str] = Field( + default_factory=list, + description=( + "Allowlist of capability IDs the agent may call (e.g., " + "'search_space.query'). Empty list = no tool access; the " + "agent must answer from the prompt alone." + ), + ) + model: str | None = Field( + default=None, + description=( + "Optional LiteLLM model identifier (e.g., " + "'anthropic/claude-sonnet-4-7'). Omitted means the " + "automation falls back to the search space's default " + "agent_llm_id." + ), + ) + output_schema: dict[str, Any] | None = Field( + default=None, + description=( + "Optional JSON Schema declaring the shape the agent must " + "return. Strongly recommended; the editor warns when " + "missing. Validated by the executor before binding to " + "``output_as``." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 3fbda8cc8..14040c20a 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -2,4 +2,18 @@ from __future__ import annotations -__all__: list[str] = [] +from .envelope import AutomationDefinition +from .execution import ExecutionBlock +from .inputs import InputsBlock +from .metadata import MetadataBlock +from .plan_step import PlanStep +from .trigger_spec import TriggerSpec + +__all__ = [ + "AutomationDefinition", + "ExecutionBlock", + "InputsBlock", + "MetadataBlock", + "PlanStep", + "TriggerSpec", +] diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py new file mode 100644 index 000000000..ccf4c53df --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -0,0 +1,89 @@ +"""``AutomationDefinition`` — the top-level envelope persisted in ``automations.definition``.""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from .execution import ExecutionBlock +from .inputs import InputsBlock +from .metadata import MetadataBlock +from .plan_step import PlanStep +from .trigger_spec import TriggerSpec + + +class AutomationDefinition(BaseModel): + """The top-level JSON shape stored in ``automations.definition``. + + This is the editable spec a user authors (or the NL generator + produces). The envelope is structural only — every nested + discriminator (``triggers[].type``, ``plan[].action``) is resolved + against the registries at validation time, so adding a new + trigger or action type does not require touching this schema. + + See ``automation-design-plan.md`` §5 for the worked example and + rationale. + """ + + model_config = ConfigDict(extra="forbid") + + schema_version: str = Field( + default="1.0", + description=( + "Schema version of the envelope itself. Migrations bump " + "this when the envelope shape changes; nested per-type " + "configs evolve independently via the registries." + ), + ) + name: str = Field( + ..., + description="Short, user-facing name shown in lists.", + min_length=1, + max_length=200, + ) + goal: str | None = Field( + default=None, + description=( + "Optional plain-language statement of what the " + "automation is for. Used by the NL generator's review " + "pass and by the UI's run dialog." + ), + ) + inputs: InputsBlock | None = Field( + default=None, + description=( + "Optional input contract. When omitted, the automation " + "accepts no inputs at fire time." + ), + ) + triggers: list[TriggerSpec] = Field( + default_factory=list, + description=( + "Triggers that fire this automation. Empty list means " + "the automation is only runnable via the manual " + "``Run now`` path." + ), + ) + plan: list[PlanStep] = Field( + ..., + description=( + "Ordered sequence of steps. Executed in array order — " + "no parallelism, no DAGs, no loops at the envelope " + "level." + ), + min_length=1, + ) + execution: ExecutionBlock = Field( + default_factory=ExecutionBlock, + description=( + "Execution defaults (timeouts, retries, concurrency, " + "budget). All fields default to safe values; the block " + "may be omitted entirely." + ), + ) + metadata: MetadataBlock = Field( + default_factory=MetadataBlock, + description=( + "Free-form metadata (tags, NL-generator breadcrumbs, " + "UI annotations). Tolerates unknown keys by design." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py new file mode 100644 index 000000000..bb80e7281 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -0,0 +1,76 @@ +"""``ExecutionBlock`` — the ``execution`` section of the automation definition.""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, ConfigDict, Field + +from .plan_step import PlanStep + + +class ExecutionBlock(BaseModel): + """The ``execution`` block of an ``AutomationDefinition``. + + Carries automation-wide defaults that individual ``PlanStep``s + can override. Every field has a sane default so an automation + definition may omit the block entirely; in that case all defaults + apply. + + ``on_failure`` is a secondary plan that runs only when the main + ``plan`` fails after retries exhaust. It uses the same + ``PlanStep`` shape as the main plan and shares the same execution + semantics. + """ + + model_config = ConfigDict(extra="forbid") + + timeout_seconds: int = Field( + default=600, + gt=0, + description=( + "Hard wall-clock cap for the entire run. The executor " + "transitions the run to ``timed_out`` when this is " + "exceeded." + ), + ) + max_retries: int = Field( + default=2, + ge=0, + description=( + "Per-step retry budget applied when a step raises a " + "retryable error. Steps may override per-step." + ), + ) + retry_backoff: Literal["exponential", "linear", "none"] = Field( + default="exponential", + description="Backoff policy between retries.", + ) + concurrency: Literal[ + "drop_if_running", "queue", "always" + ] = Field( + default="drop_if_running", + description=( + "Behaviour when a new fire arrives while a previous run " + "is still in progress. ``drop_if_running`` skips the new " + "fire, ``queue`` enqueues it, ``always`` runs it in " + "parallel." + ), + ) + budget_cap_usd: float | None = Field( + default=None, + gt=0, + description=( + "Optional mid-flight cost cap in USD. The executor kills " + "the run when accumulated cost exceeds this value. v1 " + "treats this as an advisory because cost tracking lands " + "with the executor in a later step." + ), + ) + on_failure: list[PlanStep] = Field( + default_factory=list, + description=( + "Secondary plan executed only when the main plan fails " + "after retries exhaust. Empty list means no fallback." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py new file mode 100644 index 000000000..279efc113 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -0,0 +1,43 @@ +"""``InputsBlock`` — the ``inputs`` section of the automation definition.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class InputsBlock(BaseModel): + """The ``inputs`` block of an ``AutomationDefinition``. + + Holds a JSON Schema describing what data the automation accepts at + fire time. The same schema is used by: + + - The form editor (to render the manual-run dialog). + - The dispatcher (to validate trigger payloads before enqueueing + executor work). + - The template engine (to expose ``{{ inputs.* }}`` references in + plan-step configs). + + The ``schema`` value is the JSON-Schema dict itself, not a + Pydantic model — automations express their input contract in pure + JSON Schema so it round-trips losslessly through the database and + the NL generator. + """ + + model_config = ConfigDict( + extra="forbid", + populate_by_name=True, + serialize_by_alias=True, + ) + + schema_: dict[str, Any] = Field( + ..., + alias="schema", + description=( + "JSON Schema (draft-07 compatible) describing the inputs " + "this automation accepts. Properties may use the special " + "``$last_fired_at`` default literal to bind to the " + "trigger's last fire time." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py new file mode 100644 index 000000000..dc6541983 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -0,0 +1,36 @@ +"""``MetadataBlock`` — the ``metadata`` section of the automation definition.""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class MetadataBlock(BaseModel): + """Free-form metadata attached to the automation definition. + + Unlike the rest of the envelope this block tolerates unknown keys + (``extra='allow'``) — it's a deliberate extension point for + UI annotations, NL-generator breadcrumbs, custom tags, etc. + + Two fields are first-class so the rest of the system can rely on + them without reaching into the loose extras: + + ``tags`` — used by the UI for filtering and grouping. + ``created_from_nl`` — set by the NL generator so we can later + measure how many runs came from natural-language authoring. + """ + + model_config = ConfigDict(extra="allow") + + tags: list[str] = Field( + default_factory=list, + description="UI-facing tags. No semantic meaning to the engine.", + ) + created_from_nl: bool = Field( + default=False, + description=( + "True when the definition was produced by the NL " + "generator (set automatically by the generator path; " + "human-authored definitions keep this false)." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py new file mode 100644 index 000000000..6898a0914 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -0,0 +1,86 @@ +"""``PlanStep`` — one entry in the envelope's ``plan`` array.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class PlanStep(BaseModel): + """One step in an automation's sequential plan. + + Steps run in array order, no parallelism, no DAGs, no loops. The + ``when`` Jinja expression provides conditional skip; branching is + achieved by ``when`` clauses on multiple steps. For looping or + parallel work, the user routes through ``agent_task`` and lets the + agent reason about it. + + ``config`` is dispatched against the action registry at + validation time — its shape is determined by + ``ActionDefinition.config_schema`` for the ``action`` value. + + ``output_as`` binds the step's typed output into the template + namespace for later steps, e.g. ``output_as: 'summary'`` then + ``{{ summary.bullets }}`` in a downstream step's config. + """ + + model_config = ConfigDict(extra="forbid") + + step_id: str = Field( + ..., + description=( + "Unique-within-plan identifier. Used in run logs and as " + "the default for ``output_as`` when not provided." + ), + min_length=1, + ) + action: str = Field( + ..., + description=( + "Action-type discriminator (e.g., ``agent_task``). " + "Resolved against the action registry." + ), + min_length=1, + ) + when: str | None = Field( + default=None, + description=( + "Optional Jinja expression evaluated against the run " + "context. Step is skipped when the expression is " + "falsy." + ), + ) + config: dict[str, Any] = Field( + default_factory=dict, + description=( + "Action-type-specific config. Validated against the " + "registered ``ActionDefinition.config_schema`` for " + "``action`` at definition-save time. Jinja templates " + "inside config are rendered at step-execute time." + ), + ) + output_as: str | None = Field( + default=None, + description=( + "Name to bind the step output under for downstream " + "steps. Defaults to ``step_id`` when omitted." + ), + ) + max_retries: int | None = Field( + default=None, + ge=0, + description=( + "Per-step override of the automation-level ``max_retries``. " + "Omitted means inherit from execution block." + ), + ) + timeout_seconds: int | None = Field( + default=None, + gt=0, + description=( + "Per-step override of the automation-level " + "``timeout_seconds``. Omitted means inherit from " + "execution block." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py new file mode 100644 index 000000000..827b0a315 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -0,0 +1,40 @@ +"""``TriggerSpec`` — one entry in the envelope's ``triggers`` array.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class TriggerSpec(BaseModel): + """One trigger attached to an automation, as it appears in the definition. + + The envelope keeps ``config`` as an untyped JSON object on purpose + — the per-type config schemas live in + ``app.automations.schemas.triggers`` and are dispatched at + validation time by looking up ``type`` in the trigger registry. + + This mirrors the design's "definitions are pure data" principle: + the envelope describes shape, the registry resolves names to + behaviour. + """ + + model_config = ConfigDict(extra="forbid") + + type: str = Field( + ..., + description=( + "Trigger-type discriminator (e.g., ``schedule``, ``manual``). " + "Resolved against the trigger registry." + ), + min_length=1, + ) + config: dict[str, Any] = Field( + default_factory=dict, + description=( + "Trigger-type-specific config. Validated against the " + "registered ``TriggerDefinition.config_schema`` for " + "``type`` at definition-save time." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py index 2da765bc3..847c7443b 100644 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -2,4 +2,10 @@ from __future__ import annotations -__all__: list[str] = [] +from .manual import ManualTriggerConfig +from .schedule import ScheduleTriggerConfig + +__all__ = [ + "ManualTriggerConfig", + "ScheduleTriggerConfig", +] diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/schemas/triggers/manual.py new file mode 100644 index 000000000..6e04ba062 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/triggers/manual.py @@ -0,0 +1,21 @@ +"""``ManualTriggerConfig`` — config for the ``manual`` trigger type (empty in v1).""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class ManualTriggerConfig(BaseModel): + """Config for the UI-driven ``manual`` trigger. + + Validated against ``AutomationTrigger.config`` whenever the + persisted ``type`` is ``manual``. v1 carries no configurable + fields — the "Run now" affordance simply fires this trigger with + an empty config object. The model exists so the registry dispatch + is uniform across all trigger types. + + Future versions may add fields here (e.g., a fixed prompt to + pre-fill the run dialog with) without breaking v1 payloads. + """ + + model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/schemas/triggers/schedule.py new file mode 100644 index 000000000..e7c20da3a --- /dev/null +++ b/surfsense_backend/app/automations/schemas/triggers/schedule.py @@ -0,0 +1,33 @@ +"""``ScheduleTriggerConfig`` — config for the ``schedule`` trigger type.""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class ScheduleTriggerConfig(BaseModel): + """Config for a cron-driven trigger. + + Validated against ``AutomationTrigger.config`` whenever the + persisted ``type`` is ``schedule``. The cron expression is + evaluated by Celery Beat's source; the timezone is an IANA name + (e.g., ``Africa/Kigali``) and is required so the user's cron is + unambiguous across DST boundaries. + """ + + model_config = ConfigDict(extra="forbid") + + cron: str = Field( + ..., + description=( + "Five-field cron expression. Minimum resolution is one " + "minute; the form editor warns when intervals tighter " + "than 15 minutes are used." + ), + examples=["0 9 * * 1-5"], + ) + timezone: str = Field( + ..., + description="IANA timezone name (e.g., 'Africa/Kigali', 'UTC').", + examples=["Africa/Kigali"], + ) From 7a96c0e29c2ff7521eab2289a618a95c0aa4a7c1 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:54:17 +0200 Subject: [PATCH 025/133] feat(automation): add empty Capability / Action / Trigger registries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three registries under app/automations/registries/, each as its own folder with the same SRP-per-file split (types.py for the dataclass, store.py for the in-memory dict + register/get/all functions). All three start empty; concrete entries land when the user signs off on which capabilities / actions / triggers to include (step 2). Capability (locked at v1-minimum five fields — see commit 2): - id, description, input_schema, output_schema, handler - CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - Frozen, slotted dataclass (immutable post-registration). ActionDefinition (v1-trim of design plan §4): - type, name, description, config_schema, handler - Defers output_contract (handled per-step by agent_task's config.output_schema), uses_capabilities (no static analysis needed until >1 action ships), and produces_artifacts (deferred alongside the artifact pipeline). TriggerDefinition (declarative, no handler): - type, description, config_schema, payload_schema - No handler field — firing is a single dispatcher's responsibility, not a per-trigger one. store.py contract for all three: - register_*: idempotent at process startup, raises on duplicate - get_*: returns None on miss - all_*: returns a defensive copy of the registry dict Verified by an inline smoke test (10 checks): empty initial state, registration and lookup work, duplicates raise, frozen dataclasses reject mutation, snapshots are copies, handlers are awaitable. Isolation invariant audit: grep across the full app/automations/ tree shows only three app.* imports, all of them ``from app.db import BaseModel, TimestampMixin`` in the model files. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.*, or any other business-logic module. --- .../app/automations/registries/__init__.py | 38 +++++++++++++++- .../registries/actions/__init__.py | 11 ++++- .../automations/registries/actions/store.py | 33 ++++++++++++++ .../automations/registries/actions/types.py | 44 +++++++++++++++++++ .../registries/capabilities/__init__.py | 11 ++++- .../registries/capabilities/store.py | 40 +++++++++++++++++ .../registries/capabilities/types.py | 40 +++++++++++++++++ .../registries/triggers/__init__.py | 10 ++++- .../automations/registries/triggers/store.py | 33 ++++++++++++++ .../automations/registries/triggers/types.py | 35 +++++++++++++++ 10 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 surfsense_backend/app/automations/registries/actions/store.py create mode 100644 surfsense_backend/app/automations/registries/actions/types.py create mode 100644 surfsense_backend/app/automations/registries/capabilities/store.py create mode 100644 surfsense_backend/app/automations/registries/capabilities/types.py create mode 100644 surfsense_backend/app/automations/registries/triggers/store.py create mode 100644 surfsense_backend/app/automations/registries/triggers/types.py diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index e7334cca8..47023f903 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -2,4 +2,40 @@ from __future__ import annotations -__all__: list[str] = [] +from .actions import ( + ActionDefinition, + ActionHandler, + all_actions, + get_action, + register_action, +) +from .capabilities import ( + Capability, + CapabilityHandler, + all_capabilities, + get_capability, + register_capability, +) +from .triggers import ( + TriggerDefinition, + all_triggers, + get_trigger, + register_trigger, +) + +__all__ = [ + "ActionDefinition", + "ActionHandler", + "Capability", + "CapabilityHandler", + "TriggerDefinition", + "all_actions", + "all_capabilities", + "all_triggers", + "get_action", + "get_capability", + "get_trigger", + "register_action", + "register_capability", + "register_trigger", +] diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index 6b19b7091..c6b550096 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -2,4 +2,13 @@ from __future__ import annotations -__all__: list[str] = [] +from .store import all_actions, get_action, register_action +from .types import ActionDefinition, ActionHandler + +__all__ = [ + "ActionDefinition", + "ActionHandler", + "all_actions", + "get_action", + "register_action", +] diff --git a/surfsense_backend/app/automations/registries/actions/store.py b/surfsense_backend/app/automations/registries/actions/store.py new file mode 100644 index 000000000..720243b83 --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/store.py @@ -0,0 +1,33 @@ +"""Action registry: in-memory dict + ``register_action`` API.""" + +from __future__ import annotations + +from .types import ActionDefinition + +_REGISTRY: dict[str, ActionDefinition] = {} + + +def register_action(action: ActionDefinition) -> None: + """Add an action to the in-memory registry. + + Raises ``ValueError`` on duplicate ``type`` — registration runs + once per process, so a duplicate is always a bug. + """ + + if action.type in _REGISTRY: + raise ValueError( + f"Action already registered: {action.type!r}" + ) + _REGISTRY[action.type] = action + + +def get_action(action_type: str) -> ActionDefinition | None: + """Look up one action by type. Returns ``None`` on miss.""" + + return _REGISTRY.get(action_type) + + +def all_actions() -> dict[str, ActionDefinition]: + """Snapshot of the registry as a defensive copy.""" + + return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py new file mode 100644 index 000000000..2ab2906b1 --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -0,0 +1,44 @@ +"""``ActionDefinition`` dataclass — the v1-minimum action shape.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] +"""The signature every action handler must satisfy. + +Identical in shape to ``CapabilityHandler`` — both receive a +caller-validated input dict and return an arbitrary output. The +distinction is purely architectural: capabilities are the low-level +"what SurfSense can do" surface, actions are the user-facing +building blocks composed into a plan. +""" + + +@dataclass(frozen=True, slots=True) +class ActionDefinition: + """A user-facing step type the plan editor can compose. + + v1 trims the dataclass to the five fields necessary for + registry dispatch and form rendering. The full design (§4) + includes ``output_contract``, ``uses_capabilities``, and + ``produces_artifacts``; all three are deferred until a consumer + feature requires them: + + - ``output_contract`` — the loose ``agent_task`` action declares + its output shape per-step via ``config.output_schema``, so the + action-level contract is not needed in v1. + - ``uses_capabilities`` — would let the NL generator do static + analysis of which capabilities each action invokes; deferred + because v1 ships a single (``agent_task``) action. + - ``produces_artifacts`` — deferred alongside the artifact + pipeline (see §13 decision 26). + """ + + type: str + name: str + description: str + config_schema: dict[str, Any] + handler: ActionHandler diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py index 77f9f88b7..6fa2c8246 100644 --- a/surfsense_backend/app/automations/registries/capabilities/__init__.py +++ b/surfsense_backend/app/automations/registries/capabilities/__init__.py @@ -2,4 +2,13 @@ from __future__ import annotations -__all__: list[str] = [] +from .store import all_capabilities, get_capability, register_capability +from .types import Capability, CapabilityHandler + +__all__ = [ + "Capability", + "CapabilityHandler", + "all_capabilities", + "get_capability", + "register_capability", +] diff --git a/surfsense_backend/app/automations/registries/capabilities/store.py b/surfsense_backend/app/automations/registries/capabilities/store.py new file mode 100644 index 000000000..3c8822d76 --- /dev/null +++ b/surfsense_backend/app/automations/registries/capabilities/store.py @@ -0,0 +1,40 @@ +"""Capability registry: in-memory dict + ``register_capability`` API.""" + +from __future__ import annotations + +from .types import Capability + +_REGISTRY: dict[str, Capability] = {} + + +def register_capability(capability: Capability) -> None: + """Add a capability to the in-memory registry. + + Raises ``ValueError`` on duplicate ``id`` — registration is + idempotent only at the module level (a module's + ``register_capability`` call runs once per process), so a + duplicate is always a bug. + """ + + if capability.id in _REGISTRY: + raise ValueError( + f"Capability already registered: {capability.id!r}" + ) + _REGISTRY[capability.id] = capability + + +def get_capability(capability_id: str) -> Capability | None: + """Look up one capability by id. Returns ``None`` on miss.""" + + return _REGISTRY.get(capability_id) + + +def all_capabilities() -> dict[str, Capability]: + """Snapshot of the registry as a defensive copy. + + Returned dict is safe to iterate while other code calls + ``register_capability`` (which v1 never does post-startup, but + the contract holds anyway). + """ + + return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/capabilities/types.py b/surfsense_backend/app/automations/registries/capabilities/types.py new file mode 100644 index 000000000..001f26ac1 --- /dev/null +++ b/surfsense_backend/app/automations/registries/capabilities/types.py @@ -0,0 +1,40 @@ +"""``Capability`` dataclass — the v1-minimum five-field shape.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] +"""The signature every capability handler must satisfy. + +The handler is a closure that already holds whatever runtime context +it needs (DB session, search-space scope, logger, etc.). The +registry only passes through the caller's input dict — the same dict +that was validated against ``input_schema``. +""" + + +@dataclass(frozen=True, slots=True) +class Capability: + """The unit of "what SurfSense can do," consumed by every layer. + + v1 keeps the dataclass to exactly five fields. Earlier drafts + considered ``name``, ``required_credentials``, ``side_effects``, + ``expected_duration_seconds``, and ``cost_estimate``; every one + of those has been removed until a concrete consumer feature + requires it (see ``automation-design-plan.md`` §3, decision v1). + + The handler is a ready-to-call function. It does not receive a + context argument — context is bound at registration time by the + factory that builds the closure (so a capability returned to an + agent's tool list looks identical to one returned to an + automation's action runtime). + """ + + id: str + description: str + input_schema: dict[str, Any] + output_schema: dict[str, Any] + handler: CapabilityHandler diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py index bc795b61a..f69c6fe8d 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -2,4 +2,12 @@ from __future__ import annotations -__all__: list[str] = [] +from .store import all_triggers, get_trigger, register_trigger +from .types import TriggerDefinition + +__all__ = [ + "TriggerDefinition", + "all_triggers", + "get_trigger", + "register_trigger", +] diff --git a/surfsense_backend/app/automations/registries/triggers/store.py b/surfsense_backend/app/automations/registries/triggers/store.py new file mode 100644 index 000000000..0a5fbdadb --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/store.py @@ -0,0 +1,33 @@ +"""Trigger registry: in-memory dict + ``register_trigger`` API.""" + +from __future__ import annotations + +from .types import TriggerDefinition + +_REGISTRY: dict[str, TriggerDefinition] = {} + + +def register_trigger(trigger: TriggerDefinition) -> None: + """Add a trigger to the in-memory registry. + + Raises ``ValueError`` on duplicate ``type`` — registration runs + once per process, so a duplicate is always a bug. + """ + + if trigger.type in _REGISTRY: + raise ValueError( + f"Trigger already registered: {trigger.type!r}" + ) + _REGISTRY[trigger.type] = trigger + + +def get_trigger(trigger_type: str) -> TriggerDefinition | None: + """Look up one trigger by type. Returns ``None`` on miss.""" + + return _REGISTRY.get(trigger_type) + + +def all_triggers() -> dict[str, TriggerDefinition]: + """Snapshot of the registry as a defensive copy.""" + + return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/registries/triggers/types.py new file mode 100644 index 000000000..256944823 --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/types.py @@ -0,0 +1,35 @@ +"""``TriggerDefinition`` dataclass — declarative trigger metadata, no handler.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +@dataclass(frozen=True, slots=True) +class TriggerDefinition: + """A trigger type the dispatcher knows how to fire. + + Triggers are purely declarative: the dispatcher (a single + process-wide component, not a per-type handler) reads the + ``automation_triggers`` table and decides when each row should + fire. The trigger's job here is to declare its input/output + contract: + + - ``config_schema``: JSON Schema for the persisted + ``AutomationTrigger.config`` — used by the form editor and + validated on save. + - ``payload_schema``: JSON Schema for the payload the dispatcher + will deliver to the executor at fire time (e.g., a schedule + trigger emits ``fired_at`` / ``scheduled_for`` / + ``last_fired_at``). + + No ``handler`` field — firing is a dispatcher responsibility, + not a per-trigger one. This keeps the dispatcher single and + leaves trigger types as pure metadata. + """ + + type: str + description: str + config_schema: dict[str, Any] + payload_schema: dict[str, Any] From f0e00bd3ee0ec5366ea6cea039723a68d08b9a9e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 23:01:22 +0200 Subject: [PATCH 026/133] chore(automation): trim docstrings to intent only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cut the docstrings and Field(description=...) text across the entire automations/ tree down to single-line intent statements, matching the multi_agent_chat conciseness style: - Module docstrings: one line stating what the file is. - Class docstrings: deleted when the class name + module docstring already cover intent; kept only where they add a constraint or rationale not visible in the signature. - Pydantic Field descriptions: short noun phrases / clauses, not full sentences. Reasoning that belonged in the design plan moved out of the code. - Enum values: per-value docstrings replaced with terse inline comments where the meaning isn't obvious from the name. Behaviour is unchanged. The same 33 files, same public surface, same imports — verified by re-running the 10-point registry smoke test and the 8-point schema round-trip / constraint suite from commits 9 and 10. LOC: 1180 → 691 (-42%). --- surfsense_backend/app/automations/__init__.py | 2 +- .../app/automations/persistence/__init__.py | 2 +- .../automations/persistence/enums/__init__.py | 2 +- .../persistence/enums/automation_status.py | 15 +--- .../persistence/enums/run_status.py | 16 +--- .../persistence/enums/trigger_type.py | 13 +-- .../persistence/models/__init__.py | 2 +- .../persistence/models/automation.py | 18 +--- .../app/automations/persistence/models/run.py | 18 +--- .../automations/persistence/models/trigger.py | 15 +--- .../app/automations/registries/__init__.py | 2 +- .../registries/actions/__init__.py | 2 +- .../automations/registries/actions/store.py | 18 +--- .../automations/registries/actions/types.py | 28 +------ .../registries/capabilities/__init__.py | 2 +- .../registries/capabilities/store.py | 25 +----- .../registries/capabilities/types.py | 24 +----- .../registries/triggers/__init__.py | 2 +- .../automations/registries/triggers/store.py | 18 +--- .../automations/registries/triggers/types.py | 23 +---- .../app/automations/schemas/__init__.py | 2 +- .../automations/schemas/actions/__init__.py | 2 +- .../automations/schemas/actions/agent_task.py | 49 ++--------- .../schemas/definition/__init__.py | 2 +- .../schemas/definition/envelope.py | 83 +++---------------- .../schemas/definition/execution.py | 65 ++------------- .../automations/schemas/definition/inputs.py | 26 +----- .../schemas/definition/metadata.py | 28 +------ .../schemas/definition/plan_step.py | 74 ++--------------- .../schemas/definition/trigger_spec.py | 29 +------ .../automations/schemas/triggers/__init__.py | 2 +- .../automations/schemas/triggers/manual.py | 14 +--- .../automations/schemas/triggers/schedule.py | 25 +----- 33 files changed, 80 insertions(+), 568 deletions(-) diff --git a/surfsense_backend/app/automations/__init__.py b/surfsense_backend/app/automations/__init__.py index edb7891ea..a4ce8ecc9 100644 --- a/surfsense_backend/app/automations/__init__.py +++ b/surfsense_backend/app/automations/__init__.py @@ -1,4 +1,4 @@ -"""Automations: scheduled / triggered runs of capabilities — see automation-design-plan.md.""" +"""Automations engine — see automation-design-plan.md.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py index 265742a85..4c1ea3423 100644 --- a/surfsense_backend/app/automations/persistence/__init__.py +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -1,4 +1,4 @@ -"""Persistence layer: SQLAlchemy enums under ``enums/`` and models under ``models/``.""" +"""SQLAlchemy models and enums for the automation tables.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/enums/__init__.py b/surfsense_backend/app/automations/persistence/enums/__init__.py index cf9e7dd1b..6c2cfcf1f 100644 --- a/surfsense_backend/app/automations/persistence/enums/__init__.py +++ b/surfsense_backend/app/automations/persistence/enums/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy / Python enums backing the three automation tables.""" +"""Enums for the automation tables.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/enums/automation_status.py b/surfsense_backend/app/automations/persistence/enums/automation_status.py index 3f2ca9621..aff6f4683 100644 --- a/surfsense_backend/app/automations/persistence/enums/automation_status.py +++ b/surfsense_backend/app/automations/persistence/enums/automation_status.py @@ -1,4 +1,4 @@ -"""``AutomationStatus`` — lifecycle of a stored automation definition.""" +"""Automation lifecycle status.""" from __future__ import annotations @@ -6,13 +6,6 @@ from enum import StrEnum class AutomationStatus(StrEnum): - """Status of an automation in the registry. - - ``active`` — eligible to fire from its triggers. - ``paused`` — definition retained, triggers do not fire. - ``archived`` — kept for run history only; no edits, no fires. - """ - - ACTIVE = "active" - PAUSED = "paused" - ARCHIVED = "archived" + ACTIVE = "active" # eligible to fire + PAUSED = "paused" # kept, but triggers don't fire + ARCHIVED = "archived" # read-only history diff --git a/surfsense_backend/app/automations/persistence/enums/run_status.py b/surfsense_backend/app/automations/persistence/enums/run_status.py index 0f619bd82..64dcd49e8 100644 --- a/surfsense_backend/app/automations/persistence/enums/run_status.py +++ b/surfsense_backend/app/automations/persistence/enums/run_status.py @@ -1,4 +1,4 @@ -"""``RunStatus`` — the state machine of a single ``AutomationRun``.""" +"""AutomationRun state machine: pending → running → (succeeded|failed|cancelled|timed_out).""" from __future__ import annotations @@ -6,20 +6,6 @@ from enum import StrEnum class RunStatus(StrEnum): - """Lifecycle states of an ``AutomationRun`` row. - - Transitions are linear with three terminal branches: - - pending → running → (succeeded | failed | cancelled | timed_out) - - ``pending`` — row created, executor task enqueued, work not started. - ``running`` — executor has picked up the run. - ``succeeded`` — terminal: plan completed without error. - ``failed`` — terminal: at least one step raised an unrecoverable error. - ``cancelled`` — terminal: caller asked for cancellation. - ``timed_out`` — terminal: run exceeded its configured timeout. - """ - PENDING = "pending" RUNNING = "running" SUCCEEDED = "succeeded" diff --git a/surfsense_backend/app/automations/persistence/enums/trigger_type.py b/surfsense_backend/app/automations/persistence/enums/trigger_type.py index eb06fe773..8318bfdee 100644 --- a/surfsense_backend/app/automations/persistence/enums/trigger_type.py +++ b/surfsense_backend/app/automations/persistence/enums/trigger_type.py @@ -1,4 +1,4 @@ -"""``TriggerType`` — the trigger-kind discriminator (v1 = schedule, manual).""" +"""Trigger-kind discriminator. v1: schedule | manual; webhook/event in Phase 2/3.""" from __future__ import annotations @@ -6,16 +6,5 @@ from enum import StrEnum class TriggerType(StrEnum): - """Kind of trigger an ``AutomationTrigger`` row represents. - - v1 ships two kinds: - - ``schedule`` — fires on a cron expression managed by Celery Beat. - ``manual`` — fires on demand from the UI's "Run now" affordance. - - ``webhook`` and ``event`` are deferred to Phase 2 and Phase 3 - respectively; adding them is an enum-value extension only. - """ - SCHEDULE = "schedule" MANUAL = "manual" diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py index 4aca02a03..4bc023ea3 100644 --- a/surfsense_backend/app/automations/persistence/models/__init__.py +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy models: one file per table (``automation.py``, ``trigger.py``, ``run.py``).""" +"""SQLAlchemy models, one per table.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py index fc4a1ed93..637fd2282 100644 --- a/surfsense_backend/app/automations/persistence/models/automation.py +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -1,4 +1,4 @@ -"""``Automation`` table — the editable, versioned automation definition.""" +"""``automations`` table — editable, versioned automation definition.""" from __future__ import annotations @@ -21,15 +21,6 @@ from ..enums.automation_status import AutomationStatus class Automation(BaseModel, TimestampMixin): - """The editable, versioned spec a user authors. - - The ``definition`` JSON is what the user (or the NL generator) writes - and edits. Each save bumps ``version`` by one; the previous JSON is - not kept in this row — version history is reconstructed from the - ``definition_snapshot`` column on every ``AutomationRun`` that fired - against a given version. - """ - __tablename__ = "automations" search_space_id = Column( @@ -59,12 +50,7 @@ class Automation(BaseModel, TimestampMixin): definition = Column(JSONB, nullable=False) - version = Column( - Integer, - nullable=False, - default=1, - server_default="1", - ) + version = Column(Integer, nullable=False, default=1, server_default="1") updated_at = Column( TIMESTAMP(timezone=True), diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 5c6ec93ec..9291e5da0 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -1,4 +1,4 @@ -"""``AutomationRun`` table — the immutable per-fire execution record.""" +"""``automation_runs`` table — immutable per-fire execution record.""" from __future__ import annotations @@ -18,16 +18,6 @@ from ..enums.run_status import RunStatus class AutomationRun(BaseModel, TimestampMixin): - """One execution of an automation. - - Every fire of any trigger inserts exactly one row here. The row is - immutable from the user's perspective — the executor only updates - ``status``, ``step_results``, ``output``, ``artifacts``, ``error``, - ``started_at``, ``finished_at`` as the run progresses; the - ``definition_snapshot`` is locked at fire time so the user can always - see exactly what code path executed for any historical run. - """ - __tablename__ = "automation_runs" automation_id = Column( @@ -52,18 +42,14 @@ class AutomationRun(BaseModel, TimestampMixin): index=True, ) + # locked at fire time so historical runs always show the exact code path definition_snapshot = Column(JSONB, nullable=False) trigger_payload = Column(JSONB, nullable=True) - resolved_inputs = Column(JSONB, nullable=False, server_default="{}") - step_results = Column(JSONB, nullable=False, server_default="[]") - output = Column(JSONB, nullable=True) - artifacts = Column(JSONB, nullable=False, server_default="[]") - error = Column(JSONB, nullable=True) started_at = Column(TIMESTAMP(timezone=True), nullable=True) diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 3173770d6..8dab48a6b 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -1,4 +1,4 @@ -"""``AutomationTrigger`` table — one row per (automation, trigger-instance) pair.""" +"""``automation_triggers`` table — one row per (automation, trigger-instance) pair.""" from __future__ import annotations @@ -18,14 +18,6 @@ from ..enums.trigger_type import TriggerType class AutomationTrigger(BaseModel, TimestampMixin): - """One trigger attached to an automation. - - An automation may have multiple triggers — e.g. a ``schedule`` trigger - for the autonomous path and a ``manual`` trigger backing the UI's - "Run now" affordance. Each trigger's ``config`` is validated against - the registered ``TriggerDefinition.config_schema`` for its ``type``. - """ - __tablename__ = "automation_triggers" automation_id = Column( @@ -51,7 +43,4 @@ class AutomationTrigger(BaseModel, TimestampMixin): index=True, ) - last_fired_at = Column( - TIMESTAMP(timezone=True), - nullable=True, - ) + last_fired_at = Column(TIMESTAMP(timezone=True), nullable=True) diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index 47023f903..a97595ced 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -1,4 +1,4 @@ -"""Three registries — ``capabilities/``, ``actions/``, ``triggers/`` — populated at import time.""" +"""Capability, action, and trigger registries — populated at process startup.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index c6b550096..1bb3ae9cc 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -1,4 +1,4 @@ -"""Action registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" +"""Action registry.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/actions/store.py b/surfsense_backend/app/automations/registries/actions/store.py index 720243b83..eff66c4c7 100644 --- a/surfsense_backend/app/automations/registries/actions/store.py +++ b/surfsense_backend/app/automations/registries/actions/store.py @@ -1,4 +1,4 @@ -"""Action registry: in-memory dict + ``register_action`` API.""" +"""In-memory action registry. Populated once at process startup.""" from __future__ import annotations @@ -8,26 +8,16 @@ _REGISTRY: dict[str, ActionDefinition] = {} def register_action(action: ActionDefinition) -> None: - """Add an action to the in-memory registry. - - Raises ``ValueError`` on duplicate ``type`` — registration runs - once per process, so a duplicate is always a bug. - """ - + """Register an action. Raises on duplicate type.""" if action.type in _REGISTRY: - raise ValueError( - f"Action already registered: {action.type!r}" - ) + raise ValueError(f"Action already registered: {action.type!r}") _REGISTRY[action.type] = action def get_action(action_type: str) -> ActionDefinition | None: - """Look up one action by type. Returns ``None`` on miss.""" - return _REGISTRY.get(action_type) def all_actions() -> dict[str, ActionDefinition]: - """Snapshot of the registry as a defensive copy.""" - + """Defensive snapshot of the registry.""" return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py index 2ab2906b1..13c826c66 100644 --- a/surfsense_backend/app/automations/registries/actions/types.py +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -1,4 +1,4 @@ -"""``ActionDefinition`` dataclass — the v1-minimum action shape.""" +"""``ActionDefinition`` dataclass and handler signature.""" from __future__ import annotations @@ -7,36 +7,10 @@ from dataclasses import dataclass from typing import Any ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] -"""The signature every action handler must satisfy. - -Identical in shape to ``CapabilityHandler`` — both receive a -caller-validated input dict and return an arbitrary output. The -distinction is purely architectural: capabilities are the low-level -"what SurfSense can do" surface, actions are the user-facing -building blocks composed into a plan. -""" @dataclass(frozen=True, slots=True) class ActionDefinition: - """A user-facing step type the plan editor can compose. - - v1 trims the dataclass to the five fields necessary for - registry dispatch and form rendering. The full design (§4) - includes ``output_contract``, ``uses_capabilities``, and - ``produces_artifacts``; all three are deferred until a consumer - feature requires them: - - - ``output_contract`` — the loose ``agent_task`` action declares - its output shape per-step via ``config.output_schema``, so the - action-level contract is not needed in v1. - - ``uses_capabilities`` — would let the NL generator do static - analysis of which capabilities each action invokes; deferred - because v1 ships a single (``agent_task``) action. - - ``produces_artifacts`` — deferred alongside the artifact - pipeline (see §13 decision 26). - """ - type: str name: str description: str diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py index 6fa2c8246..213303fc0 100644 --- a/surfsense_backend/app/automations/registries/capabilities/__init__.py +++ b/surfsense_backend/app/automations/registries/capabilities/__init__.py @@ -1,4 +1,4 @@ -"""Capability registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" +"""Capability registry.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/capabilities/store.py b/surfsense_backend/app/automations/registries/capabilities/store.py index 3c8822d76..4d87abe47 100644 --- a/surfsense_backend/app/automations/registries/capabilities/store.py +++ b/surfsense_backend/app/automations/registries/capabilities/store.py @@ -1,4 +1,4 @@ -"""Capability registry: in-memory dict + ``register_capability`` API.""" +"""In-memory capability registry. Populated once at process startup.""" from __future__ import annotations @@ -8,33 +8,16 @@ _REGISTRY: dict[str, Capability] = {} def register_capability(capability: Capability) -> None: - """Add a capability to the in-memory registry. - - Raises ``ValueError`` on duplicate ``id`` — registration is - idempotent only at the module level (a module's - ``register_capability`` call runs once per process), so a - duplicate is always a bug. - """ - + """Register a capability. Raises on duplicate id.""" if capability.id in _REGISTRY: - raise ValueError( - f"Capability already registered: {capability.id!r}" - ) + raise ValueError(f"Capability already registered: {capability.id!r}") _REGISTRY[capability.id] = capability def get_capability(capability_id: str) -> Capability | None: - """Look up one capability by id. Returns ``None`` on miss.""" - return _REGISTRY.get(capability_id) def all_capabilities() -> dict[str, Capability]: - """Snapshot of the registry as a defensive copy. - - Returned dict is safe to iterate while other code calls - ``register_capability`` (which v1 never does post-startup, but - the contract holds anyway). - """ - + """Defensive snapshot of the registry.""" return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/capabilities/types.py b/surfsense_backend/app/automations/registries/capabilities/types.py index 001f26ac1..2759bc809 100644 --- a/surfsense_backend/app/automations/registries/capabilities/types.py +++ b/surfsense_backend/app/automations/registries/capabilities/types.py @@ -1,4 +1,4 @@ -"""``Capability`` dataclass — the v1-minimum five-field shape.""" +"""``Capability`` dataclass and handler signature. Locked at five fields for v1.""" from __future__ import annotations @@ -7,32 +7,10 @@ from dataclasses import dataclass from typing import Any CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] -"""The signature every capability handler must satisfy. - -The handler is a closure that already holds whatever runtime context -it needs (DB session, search-space scope, logger, etc.). The -registry only passes through the caller's input dict — the same dict -that was validated against ``input_schema``. -""" @dataclass(frozen=True, slots=True) class Capability: - """The unit of "what SurfSense can do," consumed by every layer. - - v1 keeps the dataclass to exactly five fields. Earlier drafts - considered ``name``, ``required_credentials``, ``side_effects``, - ``expected_duration_seconds``, and ``cost_estimate``; every one - of those has been removed until a concrete consumer feature - requires it (see ``automation-design-plan.md`` §3, decision v1). - - The handler is a ready-to-call function. It does not receive a - context argument — context is bound at registration time by the - factory that builds the closure (so a capability returned to an - agent's tool list looks identical to one returned to an - automation's action runtime). - """ - id: str description: str input_schema: dict[str, Any] diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py index f69c6fe8d..843da5e70 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -1,4 +1,4 @@ -"""Trigger registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" +"""Trigger registry.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/triggers/store.py b/surfsense_backend/app/automations/registries/triggers/store.py index 0a5fbdadb..af0fafac7 100644 --- a/surfsense_backend/app/automations/registries/triggers/store.py +++ b/surfsense_backend/app/automations/registries/triggers/store.py @@ -1,4 +1,4 @@ -"""Trigger registry: in-memory dict + ``register_trigger`` API.""" +"""In-memory trigger registry. Populated once at process startup.""" from __future__ import annotations @@ -8,26 +8,16 @@ _REGISTRY: dict[str, TriggerDefinition] = {} def register_trigger(trigger: TriggerDefinition) -> None: - """Add a trigger to the in-memory registry. - - Raises ``ValueError`` on duplicate ``type`` — registration runs - once per process, so a duplicate is always a bug. - """ - + """Register a trigger. Raises on duplicate type.""" if trigger.type in _REGISTRY: - raise ValueError( - f"Trigger already registered: {trigger.type!r}" - ) + raise ValueError(f"Trigger already registered: {trigger.type!r}") _REGISTRY[trigger.type] = trigger def get_trigger(trigger_type: str) -> TriggerDefinition | None: - """Look up one trigger by type. Returns ``None`` on miss.""" - return _REGISTRY.get(trigger_type) def all_triggers() -> dict[str, TriggerDefinition]: - """Snapshot of the registry as a defensive copy.""" - + """Defensive snapshot of the registry.""" return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/registries/triggers/types.py index 256944823..5da081343 100644 --- a/surfsense_backend/app/automations/registries/triggers/types.py +++ b/surfsense_backend/app/automations/registries/triggers/types.py @@ -1,4 +1,4 @@ -"""``TriggerDefinition`` dataclass — declarative trigger metadata, no handler.""" +"""``TriggerDefinition`` dataclass. Declarative; firing is the dispatcher's job.""" from __future__ import annotations @@ -8,27 +8,6 @@ from typing import Any @dataclass(frozen=True, slots=True) class TriggerDefinition: - """A trigger type the dispatcher knows how to fire. - - Triggers are purely declarative: the dispatcher (a single - process-wide component, not a per-type handler) reads the - ``automation_triggers`` table and decides when each row should - fire. The trigger's job here is to declare its input/output - contract: - - - ``config_schema``: JSON Schema for the persisted - ``AutomationTrigger.config`` — used by the form editor and - validated on save. - - ``payload_schema``: JSON Schema for the payload the dispatcher - will deliver to the executor at fire time (e.g., a schedule - trigger emits ``fired_at`` / ``scheduled_for`` / - ``last_fired_at``). - - No ``handler`` field — firing is a dispatcher responsibility, - not a per-trigger one. This keeps the dispatcher single and - leaves trigger types as pure metadata. - """ - type: str description: str config_schema: dict[str, Any] diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 83a95a2a8..23f0232fb 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -1,4 +1,4 @@ -"""Pydantic schemas: definition envelope, trigger configs, action configs.""" +"""Pydantic schemas for the automation definition and per-type configs.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py index 17c257562..4149206d7 100644 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -1,4 +1,4 @@ -"""Per-action config schemas: one file per action type registered in v1.""" +"""Per-action config schemas, one per action type.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py index 74e41166a..fe9d5fcef 100644 --- a/surfsense_backend/app/automations/schemas/actions/agent_task.py +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -8,59 +8,20 @@ from pydantic import BaseModel, ConfigDict, Field class AgentTaskActionConfig(BaseModel): - """Config for an ``agent_task`` plan step. - - Validated against ``PlanStep.config`` whenever the step's - ``action`` is ``agent_task``. The step instructs the LangGraph - Deep Agent runtime to: - - 1. Receive ``prompt`` (with all preceding-step outputs and inputs - already rendered by the template engine). - 2. Run the agent with access to *exactly* the capabilities named - in ``tools`` — nothing else from the registry is visible to - this agent invocation. - 3. Return a JSON object matching ``output_schema`` (recommended; - the executor validates and re-prompts on mismatch). - - ``output_schema`` is the design's "dynamic output contract" — - instead of locking the output shape on the ActionDefinition (as - tight actions do), the user declares the shape they want for this - specific step, and the agent has to match it. - """ + """Run a LangGraph Deep Agent restricted to a scoped capability list.""" model_config = ConfigDict(extra="forbid") - prompt: str = Field( - ..., - description=( - "The task prompt rendered through the Jinja sandbox. May " - "reference automation inputs and prior-step outputs." - ), - min_length=1, - ) + prompt: str = Field(..., min_length=1, description="Task prompt; Jinja-rendered.") tools: list[str] = Field( default_factory=list, - description=( - "Allowlist of capability IDs the agent may call (e.g., " - "'search_space.query'). Empty list = no tool access; the " - "agent must answer from the prompt alone." - ), + description="Capability IDs the agent may call. Empty = no tool access.", ) model: str | None = Field( default=None, - description=( - "Optional LiteLLM model identifier (e.g., " - "'anthropic/claude-sonnet-4-7'). Omitted means the " - "automation falls back to the search space's default " - "agent_llm_id." - ), + description="LiteLLM model id. Defaults to the search space's agent_llm_id.", ) output_schema: dict[str, Any] | None = Field( default=None, - description=( - "Optional JSON Schema declaring the shape the agent must " - "return. Strongly recommended; the editor warns when " - "missing. Validated by the executor before binding to " - "``output_as``." - ), + description="JSON Schema the agent must return. Recommended.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 14040c20a..838e72f86 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -1,4 +1,4 @@ -"""Automation definition envelope: the editable structured spec users author and run.""" +"""Automation definition envelope and its building blocks.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index ccf4c53df..ccd76d612 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -1,4 +1,4 @@ -"""``AutomationDefinition`` — the top-level envelope persisted in ``automations.definition``.""" +"""``AutomationDefinition`` — top-level envelope persisted in ``automations.definition``.""" from __future__ import annotations @@ -12,78 +12,15 @@ from .trigger_spec import TriggerSpec class AutomationDefinition(BaseModel): - """The top-level JSON shape stored in ``automations.definition``. - - This is the editable spec a user authors (or the NL generator - produces). The envelope is structural only — every nested - discriminator (``triggers[].type``, ``plan[].action``) is resolved - against the registries at validation time, so adding a new - trigger or action type does not require touching this schema. - - See ``automation-design-plan.md`` §5 for the worked example and - rationale. - """ + """Top-level shape of an automation. See automation-design-plan.md §5.""" model_config = ConfigDict(extra="forbid") - schema_version: str = Field( - default="1.0", - description=( - "Schema version of the envelope itself. Migrations bump " - "this when the envelope shape changes; nested per-type " - "configs evolve independently via the registries." - ), - ) - name: str = Field( - ..., - description="Short, user-facing name shown in lists.", - min_length=1, - max_length=200, - ) - goal: str | None = Field( - default=None, - description=( - "Optional plain-language statement of what the " - "automation is for. Used by the NL generator's review " - "pass and by the UI's run dialog." - ), - ) - inputs: InputsBlock | None = Field( - default=None, - description=( - "Optional input contract. When omitted, the automation " - "accepts no inputs at fire time." - ), - ) - triggers: list[TriggerSpec] = Field( - default_factory=list, - description=( - "Triggers that fire this automation. Empty list means " - "the automation is only runnable via the manual " - "``Run now`` path." - ), - ) - plan: list[PlanStep] = Field( - ..., - description=( - "Ordered sequence of steps. Executed in array order — " - "no parallelism, no DAGs, no loops at the envelope " - "level." - ), - min_length=1, - ) - execution: ExecutionBlock = Field( - default_factory=ExecutionBlock, - description=( - "Execution defaults (timeouts, retries, concurrency, " - "budget). All fields default to safe values; the block " - "may be omitted entirely." - ), - ) - metadata: MetadataBlock = Field( - default_factory=MetadataBlock, - description=( - "Free-form metadata (tags, NL-generator breadcrumbs, " - "UI annotations). Tolerates unknown keys by design." - ), - ) + schema_version: str = "1.0" + name: str = Field(..., min_length=1, max_length=200) + goal: str | None = None + inputs: InputsBlock | None = None + triggers: list[TriggerSpec] = Field(default_factory=list) + plan: list[PlanStep] = Field(..., min_length=1) + execution: ExecutionBlock = Field(default_factory=ExecutionBlock) + metadata: MetadataBlock = Field(default_factory=MetadataBlock) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index bb80e7281..2fcbc611e 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -1,4 +1,4 @@ -"""``ExecutionBlock`` — the ``execution`` section of the automation definition.""" +"""``ExecutionBlock`` — automation-wide execution defaults (overridable per step).""" from __future__ import annotations @@ -10,67 +10,16 @@ from .plan_step import PlanStep class ExecutionBlock(BaseModel): - """The ``execution`` block of an ``AutomationDefinition``. - - Carries automation-wide defaults that individual ``PlanStep``s - can override. Every field has a sane default so an automation - definition may omit the block entirely; in that case all defaults - apply. - - ``on_failure`` is a secondary plan that runs only when the main - ``plan`` fails after retries exhaust. It uses the same - ``PlanStep`` shape as the main plan and shares the same execution - semantics. - """ - model_config = ConfigDict(extra="forbid") - timeout_seconds: int = Field( - default=600, - gt=0, - description=( - "Hard wall-clock cap for the entire run. The executor " - "transitions the run to ``timed_out`` when this is " - "exceeded." - ), - ) - max_retries: int = Field( - default=2, - ge=0, - description=( - "Per-step retry budget applied when a step raises a " - "retryable error. Steps may override per-step." - ), - ) - retry_backoff: Literal["exponential", "linear", "none"] = Field( - default="exponential", - description="Backoff policy between retries.", - ) - concurrency: Literal[ - "drop_if_running", "queue", "always" - ] = Field( - default="drop_if_running", - description=( - "Behaviour when a new fire arrives while a previous run " - "is still in progress. ``drop_if_running`` skips the new " - "fire, ``queue`` enqueues it, ``always`` runs it in " - "parallel." - ), - ) + timeout_seconds: int = Field(default=600, gt=0, description="Wall-clock cap for the run.") + max_retries: int = Field(default=2, ge=0, description="Per-step retry budget.") + retry_backoff: Literal["exponential", "linear", "none"] = "exponential" + concurrency: Literal["drop_if_running", "queue", "always"] = "drop_if_running" budget_cap_usd: float | None = Field( - default=None, - gt=0, - description=( - "Optional mid-flight cost cap in USD. The executor kills " - "the run when accumulated cost exceeds this value. v1 " - "treats this as an advisory because cost tracking lands " - "with the executor in a later step." - ), + default=None, gt=0, description="Kill the run when accumulated cost exceeds this." ) on_failure: list[PlanStep] = Field( default_factory=list, - description=( - "Secondary plan executed only when the main plan fails " - "after retries exhaust. Empty list means no fallback." - ), + description="Steps run when the main plan fails after retries.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py index 279efc113..52aed4e90 100644 --- a/surfsense_backend/app/automations/schemas/definition/inputs.py +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -1,4 +1,4 @@ -"""``InputsBlock`` — the ``inputs`` section of the automation definition.""" +"""``InputsBlock`` — JSON Schema for inputs an automation accepts at fire time.""" from __future__ import annotations @@ -8,23 +8,6 @@ from pydantic import BaseModel, ConfigDict, Field class InputsBlock(BaseModel): - """The ``inputs`` block of an ``AutomationDefinition``. - - Holds a JSON Schema describing what data the automation accepts at - fire time. The same schema is used by: - - - The form editor (to render the manual-run dialog). - - The dispatcher (to validate trigger payloads before enqueueing - executor work). - - The template engine (to expose ``{{ inputs.* }}`` references in - plan-step configs). - - The ``schema`` value is the JSON-Schema dict itself, not a - Pydantic model — automations express their input contract in pure - JSON Schema so it round-trips losslessly through the database and - the NL generator. - """ - model_config = ConfigDict( extra="forbid", populate_by_name=True, @@ -34,10 +17,5 @@ class InputsBlock(BaseModel): schema_: dict[str, Any] = Field( ..., alias="schema", - description=( - "JSON Schema (draft-07 compatible) describing the inputs " - "this automation accepts. Properties may use the special " - "``$last_fired_at`` default literal to bind to the " - "trigger's last fire time." - ), + description="JSON Schema (draft-07) for accepted inputs.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py index dc6541983..61d7af390 100644 --- a/surfsense_backend/app/automations/schemas/definition/metadata.py +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -1,4 +1,4 @@ -"""``MetadataBlock`` — the ``metadata`` section of the automation definition.""" +"""``MetadataBlock`` — free-form metadata on a definition. Extra keys allowed.""" from __future__ import annotations @@ -6,31 +6,9 @@ from pydantic import BaseModel, ConfigDict, Field class MetadataBlock(BaseModel): - """Free-form metadata attached to the automation definition. - - Unlike the rest of the envelope this block tolerates unknown keys - (``extra='allow'``) — it's a deliberate extension point for - UI annotations, NL-generator breadcrumbs, custom tags, etc. - - Two fields are first-class so the rest of the system can rely on - them without reaching into the loose extras: - - ``tags`` — used by the UI for filtering and grouping. - ``created_from_nl`` — set by the NL generator so we can later - measure how many runs came from natural-language authoring. - """ - model_config = ConfigDict(extra="allow") - tags: list[str] = Field( - default_factory=list, - description="UI-facing tags. No semantic meaning to the engine.", - ) + tags: list[str] = Field(default_factory=list) created_from_nl: bool = Field( - default=False, - description=( - "True when the definition was produced by the NL " - "generator (set automatically by the generator path; " - "human-authored definitions keep this false)." - ), + default=False, description="True when produced by the NL generator." ) diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py index 6898a0914..6a0bf9a1b 100644 --- a/surfsense_backend/app/automations/schemas/definition/plan_step.py +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -1,4 +1,4 @@ -"""``PlanStep`` — one entry in the envelope's ``plan`` array.""" +"""``PlanStep`` — one step in the sequential plan.""" from __future__ import annotations @@ -8,79 +8,21 @@ from pydantic import BaseModel, ConfigDict, Field class PlanStep(BaseModel): - """One step in an automation's sequential plan. - - Steps run in array order, no parallelism, no DAGs, no loops. The - ``when`` Jinja expression provides conditional skip; branching is - achieved by ``when`` clauses on multiple steps. For looping or - parallel work, the user routes through ``agent_task`` and lets the - agent reason about it. - - ``config`` is dispatched against the action registry at - validation time — its shape is determined by - ``ActionDefinition.config_schema`` for the ``action`` value. - - ``output_as`` binds the step's typed output into the template - namespace for later steps, e.g. ``output_as: 'summary'`` then - ``{{ summary.bullets }}`` in a downstream step's config. - """ - model_config = ConfigDict(extra="forbid") - step_id: str = Field( - ..., - description=( - "Unique-within-plan identifier. Used in run logs and as " - "the default for ``output_as`` when not provided." - ), - min_length=1, - ) - action: str = Field( - ..., - description=( - "Action-type discriminator (e.g., ``agent_task``). " - "Resolved against the action registry." - ), - min_length=1, - ) + step_id: str = Field(..., min_length=1, description="Unique within the plan.") + action: str = Field(..., min_length=1, description="Action type; resolved via registry.") when: str | None = Field( default=None, - description=( - "Optional Jinja expression evaluated against the run " - "context. Step is skipped when the expression is " - "falsy." - ), + description="Optional Jinja expression; step is skipped when falsy.", ) config: dict[str, Any] = Field( default_factory=dict, - description=( - "Action-type-specific config. Validated against the " - "registered ``ActionDefinition.config_schema`` for " - "``action`` at definition-save time. Jinja templates " - "inside config are rendered at step-execute time." - ), + description="Action-type-specific config; Jinja-rendered at execute time.", ) output_as: str | None = Field( default=None, - description=( - "Name to bind the step output under for downstream " - "steps. Defaults to ``step_id`` when omitted." - ), - ) - max_retries: int | None = Field( - default=None, - ge=0, - description=( - "Per-step override of the automation-level ``max_retries``. " - "Omitted means inherit from execution block." - ), - ) - timeout_seconds: int | None = Field( - default=None, - gt=0, - description=( - "Per-step override of the automation-level " - "``timeout_seconds``. Omitted means inherit from " - "execution block." - ), + description="Bind step output under this name. Defaults to step_id.", ) + max_retries: int | None = Field(default=None, ge=0) + timeout_seconds: int | None = Field(default=None, gt=0) diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py index 827b0a315..0fdf1f35a 100644 --- a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -1,4 +1,4 @@ -"""``TriggerSpec`` — one entry in the envelope's ``triggers`` array.""" +"""``TriggerSpec`` — one entry in the definition's ``triggers[]`` array.""" from __future__ import annotations @@ -8,33 +8,10 @@ from pydantic import BaseModel, ConfigDict, Field class TriggerSpec(BaseModel): - """One trigger attached to an automation, as it appears in the definition. - - The envelope keeps ``config`` as an untyped JSON object on purpose - — the per-type config schemas live in - ``app.automations.schemas.triggers`` and are dispatched at - validation time by looking up ``type`` in the trigger registry. - - This mirrors the design's "definitions are pure data" principle: - the envelope describes shape, the registry resolves names to - behaviour. - """ - model_config = ConfigDict(extra="forbid") - type: str = Field( - ..., - description=( - "Trigger-type discriminator (e.g., ``schedule``, ``manual``). " - "Resolved against the trigger registry." - ), - min_length=1, - ) + type: str = Field(..., min_length=1, description="Trigger type; resolved via registry.") config: dict[str, Any] = Field( default_factory=dict, - description=( - "Trigger-type-specific config. Validated against the " - "registered ``TriggerDefinition.config_schema`` for " - "``type`` at definition-save time." - ), + description="Type-specific config; validated against the trigger's schema.", ) diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py index 847c7443b..0cd8bc38e 100644 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -1,4 +1,4 @@ -"""Per-trigger config schemas: one file per trigger type registered in v1.""" +"""Per-trigger config schemas, one per trigger type.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/schemas/triggers/manual.py index 6e04ba062..bf14f80b6 100644 --- a/surfsense_backend/app/automations/schemas/triggers/manual.py +++ b/surfsense_backend/app/automations/schemas/triggers/manual.py @@ -1,4 +1,4 @@ -"""``ManualTriggerConfig`` — config for the ``manual`` trigger type (empty in v1).""" +"""``ManualTriggerConfig`` — config for the ``manual`` trigger (empty in v1).""" from __future__ import annotations @@ -6,16 +6,4 @@ from pydantic import BaseModel, ConfigDict class ManualTriggerConfig(BaseModel): - """Config for the UI-driven ``manual`` trigger. - - Validated against ``AutomationTrigger.config`` whenever the - persisted ``type`` is ``manual``. v1 carries no configurable - fields — the "Run now" affordance simply fires this trigger with - an empty config object. The model exists so the registry dispatch - is uniform across all trigger types. - - Future versions may add fields here (e.g., a fixed prompt to - pre-fill the run dialog with) without breaking v1 payloads. - """ - model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/schemas/triggers/schedule.py index e7c20da3a..9d8c7d38d 100644 --- a/surfsense_backend/app/automations/schemas/triggers/schedule.py +++ b/surfsense_backend/app/automations/schemas/triggers/schedule.py @@ -6,28 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field class ScheduleTriggerConfig(BaseModel): - """Config for a cron-driven trigger. - - Validated against ``AutomationTrigger.config`` whenever the - persisted ``type`` is ``schedule``. The cron expression is - evaluated by Celery Beat's source; the timezone is an IANA name - (e.g., ``Africa/Kigali``) and is required so the user's cron is - unambiguous across DST boundaries. - """ - model_config = ConfigDict(extra="forbid") - cron: str = Field( - ..., - description=( - "Five-field cron expression. Minimum resolution is one " - "minute; the form editor warns when intervals tighter " - "than 15 minutes are used." - ), - examples=["0 9 * * 1-5"], - ) - timezone: str = Field( - ..., - description="IANA timezone name (e.g., 'Africa/Kigali', 'UTC').", - examples=["Africa/Kigali"], - ) + cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) + timezone: str = Field(..., description="IANA timezone.", examples=["Africa/Kigali"]) From 35117a952dd9ec5b6709f668dff0109e3c3d0d0e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 11:41:32 +0200 Subject: [PATCH 027/133] refactor(automation): drop agent_session_id from AutomationRun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A run can contain zero, one, or N agent_task steps. A single agent_session_id at the run level holds at most one of them, so the column is the wrong shape for the data. Per-step session ids (LangGraph thread/checkpoint reference for an agent_task step) live inside step_results[i] alongside the rest of the per-step bag (status, timings, output). Each agent step records its own; non-agent steps record nothing. Run-level "primary session" is a UI concern, not a schema concern. Trade-off: trace -> run reverse lookup is now a JSONB query, not an index hit. Usually traversal goes run -> trace; if the reverse becomes hot we add a GIN index on step_results or a generated column — both additive. Changes: - AutomationRun: drop the agent_session_id column; module docstring notes where per-step session ids now live. - Migration 144: drop the column from the CREATE TABLE; downgrade unchanged. Safe to edit migration 144 in place (vs. add 145 with ALTER ... DROP): this branch has not shipped and the table has never existed in any deployed database. --- .../alembic/versions/144_add_automation_tables.py | 1 - .../app/automations/persistence/models/run.py | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 6aa208dc1..acfa3db77 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -125,7 +125,6 @@ def upgrade() -> None: error JSONB, started_at TIMESTAMP WITH TIME ZONE, finished_at TIMESTAMP WITH TIME ZONE, - agent_session_id VARCHAR(200), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); """ diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 9291e5da0..43a1de07d 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -1,4 +1,9 @@ -"""``automation_runs`` table — immutable per-fire execution record.""" +"""``automation_runs`` table — immutable per-fire execution record. + +Per-step metadata (incl. any LangGraph session id for an ``agent_task`` step) +lives inside ``step_results[i]``, since a single run may contain zero, one, +or N agent steps. +""" from __future__ import annotations @@ -8,7 +13,6 @@ from sqlalchemy import ( Enum as SQLAlchemyEnum, ForeignKey, Integer, - String, ) from sqlalchemy.dialects.postgresql import JSONB @@ -54,5 +58,3 @@ class AutomationRun(BaseModel, TimestampMixin): started_at = Column(TIMESTAMP(timezone=True), nullable=True) finished_at = Column(TIMESTAMP(timezone=True), nullable=True) - - agent_session_id = Column(String(200), nullable=True) From a4fbfd8c0d77bc9f11ef26d87c5d64b283109829 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 11:45:04 +0200 Subject: [PATCH 028/133] chore(automation): tighten run.py + envelope.py docstrings Re-apply the trim style after the prior refactor commit re-introduced a multi-line docstring on AutomationRun. - AutomationRun: drop the four-line docstring explaining where per-step session ids live; move the note to a single-line inline comment right above ``step_results`` where it's actionable. - AutomationDefinition: drop the design-plan cross-reference; the module docstring already establishes what the file is. No behaviour change. --- .../app/automations/persistence/models/run.py | 9 +++------ .../app/automations/schemas/definition/envelope.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 43a1de07d..45da6a39d 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -1,9 +1,4 @@ -"""``automation_runs`` table — immutable per-fire execution record. - -Per-step metadata (incl. any LangGraph session id for an ``agent_task`` step) -lives inside ``step_results[i]``, since a single run may contain zero, one, -or N agent steps. -""" +"""``automation_runs`` table — immutable per-fire execution record.""" from __future__ import annotations @@ -51,6 +46,8 @@ class AutomationRun(BaseModel, TimestampMixin): trigger_payload = Column(JSONB, nullable=True) resolved_inputs = Column(JSONB, nullable=False, server_default="{}") + # one entry per executed step; agent_task entries carry their own + # `agent_session_id` (LangGraph thread reference) inside this JSONB step_results = Column(JSONB, nullable=False, server_default="[]") output = Column(JSONB, nullable=True) artifacts = Column(JSONB, nullable=False, server_default="[]") diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index ccd76d612..ffc45a0cd 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -12,7 +12,7 @@ from .trigger_spec import TriggerSpec class AutomationDefinition(BaseModel): - """Top-level shape of an automation. See automation-design-plan.md §5.""" + """Top-level shape of an automation.""" model_config = ConfigDict(extra="forbid") From fe32cd35ed9e1f407ac8b9cc46b9acaf57742790 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:18 +0200 Subject: [PATCH 029/133] refactor(automation): rename trigger config column to params --- surfsense_backend/alembic/versions/144_add_automation_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index acfa3db77..8b59ee969 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -86,7 +86,7 @@ def upgrade() -> None: automation_id INTEGER NOT NULL REFERENCES automations(id) ON DELETE CASCADE, type automation_trigger_type NOT NULL, - config JSONB NOT NULL, + params JSONB NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, last_fired_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() From c8a89ccac8536d42d679020d3fcc70d8ca234184 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:22 +0200 Subject: [PATCH 030/133] refactor(automation): rename trigger model config to params --- surfsense_backend/app/automations/persistence/__init__.py | 2 +- .../app/automations/persistence/models/__init__.py | 2 +- surfsense_backend/app/automations/persistence/models/run.py | 2 +- surfsense_backend/app/automations/persistence/models/trigger.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py index 4c1ea3423..b10aef03d 100644 --- a/surfsense_backend/app/automations/persistence/__init__.py +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy models and enums for the automation tables.""" +"""Models and enums for the automation tables.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py index 4bc023ea3..8b985f025 100644 --- a/surfsense_backend/app/automations/persistence/models/__init__.py +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy models, one per table.""" +"""Models, one per table.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 45da6a39d..118085b1d 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -47,7 +47,7 @@ class AutomationRun(BaseModel, TimestampMixin): trigger_payload = Column(JSONB, nullable=True) resolved_inputs = Column(JSONB, nullable=False, server_default="{}") # one entry per executed step; agent_task entries carry their own - # `agent_session_id` (LangGraph thread reference) inside this JSONB + # `agent_session_id` inside their entry step_results = Column(JSONB, nullable=False, server_default="[]") output = Column(JSONB, nullable=True) artifacts = Column(JSONB, nullable=False, server_default="[]") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 8dab48a6b..55affeabc 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -33,7 +33,7 @@ class AutomationTrigger(BaseModel, TimestampMixin): index=True, ) - config = Column(JSONB, nullable=False) + params = Column(JSONB, nullable=False) enabled = Column( Boolean, From 9fa35f21cfea0d09c2a148502810fea7f31d0645 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:26 +0200 Subject: [PATCH 031/133] refactor(automation): rename schema config to params, drop dead fields --- .../app/automations/schemas/__init__.py | 12 ++++++------ .../app/automations/schemas/actions/__init__.py | 6 +++--- .../app/automations/schemas/actions/agent_task.py | 14 +++++++------- .../automations/schemas/definition/execution.py | 3 --- .../app/automations/schemas/definition/inputs.py | 2 +- .../app/automations/schemas/definition/metadata.py | 3 --- .../automations/schemas/definition/plan_step.py | 6 +++--- .../automations/schemas/definition/trigger_spec.py | 4 ++-- .../app/automations/schemas/triggers/__init__.py | 10 +++++----- .../app/automations/schemas/triggers/manual.py | 4 ++-- .../app/automations/schemas/triggers/schedule.py | 4 ++-- 11 files changed, 31 insertions(+), 37 deletions(-) diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 23f0232fb..2bb0060ba 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -1,8 +1,8 @@ -"""Pydantic schemas for the automation definition and per-type configs.""" +"""Schemas for the automation definition and per-type configs.""" from __future__ import annotations -from .actions import AgentTaskActionConfig +from .actions import AgentTaskActionParams from .definition import ( AutomationDefinition, ExecutionBlock, @@ -11,16 +11,16 @@ from .definition import ( PlanStep, TriggerSpec, ) -from .triggers import ManualTriggerConfig, ScheduleTriggerConfig +from .triggers import ManualTriggerParams, ScheduleTriggerParams __all__ = [ - "AgentTaskActionConfig", + "AgentTaskActionParams", "AutomationDefinition", "ExecutionBlock", "InputsBlock", - "ManualTriggerConfig", + "ManualTriggerParams", "MetadataBlock", "PlanStep", - "ScheduleTriggerConfig", + "ScheduleTriggerParams", "TriggerSpec", ] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py index 4149206d7..c51d33b6a 100644 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -1,9 +1,9 @@ -"""Per-action config schemas, one per action type.""" +"""Per-action params schemas, one per action type.""" from __future__ import annotations -from .agent_task import AgentTaskActionConfig +from .agent_task import AgentTaskActionParams __all__ = [ - "AgentTaskActionConfig", + "AgentTaskActionParams", ] diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py index fe9d5fcef..348db8095 100644 --- a/surfsense_backend/app/automations/schemas/actions/agent_task.py +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -1,4 +1,4 @@ -"""``AgentTaskActionConfig`` — config for the ``agent_task`` action type.""" +"""``AgentTaskActionParams`` — params for the ``agent_task`` action type.""" from __future__ import annotations @@ -7,21 +7,21 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -class AgentTaskActionConfig(BaseModel): - """Run a LangGraph Deep Agent restricted to a scoped capability list.""" +class AgentTaskActionParams(BaseModel): + """Run an agent task with a scoped tool allowlist.""" model_config = ConfigDict(extra="forbid") - prompt: str = Field(..., min_length=1, description="Task prompt; Jinja-rendered.") + prompt: str = Field(..., min_length=1, description="Task prompt; rendered at execute time.") tools: list[str] = Field( default_factory=list, - description="Capability IDs the agent may call. Empty = no tool access.", + description="Tool identifiers the agent may call. Empty = no tool access.", ) model: str | None = Field( default=None, - description="LiteLLM model id. Defaults to the search space's agent_llm_id.", + description="Model identifier. Defaults to the search space's agent_llm_id.", ) output_schema: dict[str, Any] | None = Field( default=None, - description="JSON Schema the agent must return. Recommended.", + description="JSON Schema (draft 2020-12) the agent must return. Recommended.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index 2fcbc611e..d5f31364c 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -16,9 +16,6 @@ class ExecutionBlock(BaseModel): max_retries: int = Field(default=2, ge=0, description="Per-step retry budget.") retry_backoff: Literal["exponential", "linear", "none"] = "exponential" concurrency: Literal["drop_if_running", "queue", "always"] = "drop_if_running" - budget_cap_usd: float | None = Field( - default=None, gt=0, description="Kill the run when accumulated cost exceeds this." - ) on_failure: list[PlanStep] = Field( default_factory=list, description="Steps run when the main plan fails after retries.", diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py index 52aed4e90..b0b1a9414 100644 --- a/surfsense_backend/app/automations/schemas/definition/inputs.py +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -17,5 +17,5 @@ class InputsBlock(BaseModel): schema_: dict[str, Any] = Field( ..., alias="schema", - description="JSON Schema (draft-07) for accepted inputs.", + description="JSON Schema (draft 2020-12) for accepted inputs.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py index 61d7af390..9b3722430 100644 --- a/surfsense_backend/app/automations/schemas/definition/metadata.py +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -9,6 +9,3 @@ class MetadataBlock(BaseModel): model_config = ConfigDict(extra="allow") tags: list[str] = Field(default_factory=list) - created_from_nl: bool = Field( - default=False, description="True when produced by the NL generator." - ) diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py index 6a0bf9a1b..5d16f1f3e 100644 --- a/surfsense_backend/app/automations/schemas/definition/plan_step.py +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -14,11 +14,11 @@ class PlanStep(BaseModel): action: str = Field(..., min_length=1, description="Action type; resolved via registry.") when: str | None = Field( default=None, - description="Optional Jinja expression; step is skipped when falsy.", + description="Optional predicate; step is skipped when falsy.", ) - config: dict[str, Any] = Field( + params: dict[str, Any] = Field( default_factory=dict, - description="Action-type-specific config; Jinja-rendered at execute time.", + description="Action-type-specific params; rendered at execute time.", ) output_as: str | None = Field( default=None, diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py index 0fdf1f35a..a359a2f63 100644 --- a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -11,7 +11,7 @@ class TriggerSpec(BaseModel): model_config = ConfigDict(extra="forbid") type: str = Field(..., min_length=1, description="Trigger type; resolved via registry.") - config: dict[str, Any] = Field( + params: dict[str, Any] = Field( default_factory=dict, - description="Type-specific config; validated against the trigger's schema.", + description="Type-specific params; validated against the trigger's schema.", ) diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py index 0cd8bc38e..3ddd26f95 100644 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -1,11 +1,11 @@ -"""Per-trigger config schemas, one per trigger type.""" +"""Per-trigger params schemas, one per trigger type.""" from __future__ import annotations -from .manual import ManualTriggerConfig -from .schedule import ScheduleTriggerConfig +from .manual import ManualTriggerParams +from .schedule import ScheduleTriggerParams __all__ = [ - "ManualTriggerConfig", - "ScheduleTriggerConfig", + "ManualTriggerParams", + "ScheduleTriggerParams", ] diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/schemas/triggers/manual.py index bf14f80b6..577655086 100644 --- a/surfsense_backend/app/automations/schemas/triggers/manual.py +++ b/surfsense_backend/app/automations/schemas/triggers/manual.py @@ -1,9 +1,9 @@ -"""``ManualTriggerConfig`` — config for the ``manual`` trigger (empty in v1).""" +"""``ManualTriggerParams`` — params for the ``manual`` trigger (empty in v1).""" from __future__ import annotations from pydantic import BaseModel, ConfigDict -class ManualTriggerConfig(BaseModel): +class ManualTriggerParams(BaseModel): model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/schemas/triggers/schedule.py index 9d8c7d38d..0418bd1d9 100644 --- a/surfsense_backend/app/automations/schemas/triggers/schedule.py +++ b/surfsense_backend/app/automations/schemas/triggers/schedule.py @@ -1,11 +1,11 @@ -"""``ScheduleTriggerConfig`` — config for the ``schedule`` trigger type.""" +"""``ScheduleTriggerParams`` — params for the ``schedule`` trigger type.""" from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -class ScheduleTriggerConfig(BaseModel): +class ScheduleTriggerParams(BaseModel): model_config = ConfigDict(extra="forbid") cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) From 7ac99b89a0ddf4f8f0d6f0d08b4fac66b7e2c1a2 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:30 +0200 Subject: [PATCH 032/133] refactor(automation): drop Capability registry --- .../app/automations/registries/__init__.py | 14 +---------- .../automations/registries/actions/types.py | 2 +- .../registries/capabilities/__init__.py | 14 ----------- .../registries/capabilities/store.py | 23 ------------------- .../registries/capabilities/types.py | 18 --------------- .../automations/registries/triggers/types.py | 2 +- 6 files changed, 3 insertions(+), 70 deletions(-) delete mode 100644 surfsense_backend/app/automations/registries/capabilities/__init__.py delete mode 100644 surfsense_backend/app/automations/registries/capabilities/store.py delete mode 100644 surfsense_backend/app/automations/registries/capabilities/types.py diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index a97595ced..f497caf59 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -1,4 +1,4 @@ -"""Capability, action, and trigger registries — populated at process startup.""" +"""Action and trigger registries — populated at process startup.""" from __future__ import annotations @@ -9,13 +9,6 @@ from .actions import ( get_action, register_action, ) -from .capabilities import ( - Capability, - CapabilityHandler, - all_capabilities, - get_capability, - register_capability, -) from .triggers import ( TriggerDefinition, all_triggers, @@ -26,16 +19,11 @@ from .triggers import ( __all__ = [ "ActionDefinition", "ActionHandler", - "Capability", - "CapabilityHandler", "TriggerDefinition", "all_actions", - "all_capabilities", "all_triggers", "get_action", - "get_capability", "get_trigger", "register_action", - "register_capability", "register_trigger", ] diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py index 13c826c66..99f94ae7c 100644 --- a/surfsense_backend/app/automations/registries/actions/types.py +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -14,5 +14,5 @@ class ActionDefinition: type: str name: str description: str - config_schema: dict[str, Any] + params_schema: dict[str, Any] handler: ActionHandler diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py deleted file mode 100644 index 213303fc0..000000000 --- a/surfsense_backend/app/automations/registries/capabilities/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Capability registry.""" - -from __future__ import annotations - -from .store import all_capabilities, get_capability, register_capability -from .types import Capability, CapabilityHandler - -__all__ = [ - "Capability", - "CapabilityHandler", - "all_capabilities", - "get_capability", - "register_capability", -] diff --git a/surfsense_backend/app/automations/registries/capabilities/store.py b/surfsense_backend/app/automations/registries/capabilities/store.py deleted file mode 100644 index 4d87abe47..000000000 --- a/surfsense_backend/app/automations/registries/capabilities/store.py +++ /dev/null @@ -1,23 +0,0 @@ -"""In-memory capability registry. Populated once at process startup.""" - -from __future__ import annotations - -from .types import Capability - -_REGISTRY: dict[str, Capability] = {} - - -def register_capability(capability: Capability) -> None: - """Register a capability. Raises on duplicate id.""" - if capability.id in _REGISTRY: - raise ValueError(f"Capability already registered: {capability.id!r}") - _REGISTRY[capability.id] = capability - - -def get_capability(capability_id: str) -> Capability | None: - return _REGISTRY.get(capability_id) - - -def all_capabilities() -> dict[str, Capability]: - """Defensive snapshot of the registry.""" - return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/capabilities/types.py b/surfsense_backend/app/automations/registries/capabilities/types.py deleted file mode 100644 index 2759bc809..000000000 --- a/surfsense_backend/app/automations/registries/capabilities/types.py +++ /dev/null @@ -1,18 +0,0 @@ -"""``Capability`` dataclass and handler signature. Locked at five fields for v1.""" - -from __future__ import annotations - -from collections.abc import Awaitable, Callable -from dataclasses import dataclass -from typing import Any - -CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - - -@dataclass(frozen=True, slots=True) -class Capability: - id: str - description: str - input_schema: dict[str, Any] - output_schema: dict[str, Any] - handler: CapabilityHandler diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/registries/triggers/types.py index 5da081343..783bd7842 100644 --- a/surfsense_backend/app/automations/registries/triggers/types.py +++ b/surfsense_backend/app/automations/registries/triggers/types.py @@ -10,5 +10,5 @@ from typing import Any class TriggerDefinition: type: str description: str - config_schema: dict[str, Any] + params_schema: dict[str, Any] payload_schema: dict[str, Any] From 7f4c1c25abad3531ecb54191aef9b9323feaacf9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:45:32 +0200 Subject: [PATCH 033/133] feat(automation): wire SQLAlchemy relationships on both sides --- .../persistence/models/automation.py | 16 ++++++++++ .../app/automations/persistence/models/run.py | 4 +++ .../automations/persistence/models/trigger.py | 8 +++++ surfsense_backend/app/db.py | 32 +++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py index 637fd2282..ee86851c1 100644 --- a/surfsense_backend/app/automations/persistence/models/automation.py +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -14,6 +14,7 @@ from sqlalchemy import ( Text, ) from sqlalchemy.dialects.postgresql import JSONB, UUID +from sqlalchemy.orm import relationship from app.db import BaseModel, TimestampMixin @@ -59,3 +60,18 @@ class Automation(BaseModel, TimestampMixin): onupdate=lambda: datetime.now(UTC), index=True, ) + + search_space = relationship("SearchSpace", back_populates="automations") + created_by = relationship("User", back_populates="automations") + triggers = relationship( + "AutomationTrigger", + back_populates="automation", + cascade="all, delete-orphan", + passive_deletes=True, + ) + runs = relationship( + "AutomationRun", + back_populates="automation", + cascade="all, delete-orphan", + passive_deletes=True, + ) diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 118085b1d..fdc355e8f 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -10,6 +10,7 @@ from sqlalchemy import ( Integer, ) from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import relationship from app.db import BaseModel, TimestampMixin @@ -55,3 +56,6 @@ class AutomationRun(BaseModel, TimestampMixin): started_at = Column(TIMESTAMP(timezone=True), nullable=True) finished_at = Column(TIMESTAMP(timezone=True), nullable=True) + + automation = relationship("Automation", back_populates="runs") + trigger = relationship("AutomationTrigger", back_populates="runs") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 55affeabc..7582234d4 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -11,6 +11,7 @@ from sqlalchemy import ( Integer, ) from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import relationship from app.db import BaseModel, TimestampMixin @@ -44,3 +45,10 @@ class AutomationTrigger(BaseModel, TimestampMixin): ) last_fired_at = Column(TIMESTAMP(timezone=True), nullable=True) + + automation = relationship("Automation", back_populates="triggers") + runs = relationship( + "AutomationRun", + back_populates="trigger", + passive_deletes=True, + ) diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 9fc27fb1f..71466495b 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -1533,6 +1533,14 @@ class SearchSpace(BaseModel, TimestampMixin): cascade="all, delete-orphan", ) + automations = relationship( + "Automation", + back_populates="search_space", + order_by="Automation.id", + cascade="all, delete-orphan", + passive_deletes=True, + ) + # RBAC relationships roles = relationship( "SearchSpaceRole", @@ -2125,6 +2133,13 @@ if config.AUTH_TYPE == "GOOGLE": passive_deletes=True, ) + # Automations created by this user + automations = relationship( + "Automation", + back_populates="created_by", + passive_deletes=True, + ) + # Incentive tasks completed by this user incentive_tasks = relationship( "UserIncentiveTask", @@ -2257,6 +2272,13 @@ else: passive_deletes=True, ) + # Automations created by this user + automations = relationship( + "Automation", + back_populates="created_by", + passive_deletes=True, + ) + # Incentive tasks completed by this user incentive_tasks = relationship( "UserIncentiveTask", @@ -2560,6 +2582,16 @@ class RefreshToken(Base, TimestampMixin): return not self.is_expired and not self.is_revoked +# Register model packages that live outside this file so their classes +# are present in Base.metadata before configure_mappers() resolves any +# string-based relationship() references. +from app.automations.persistence import ( # noqa: E402, F401 + Automation, + AutomationRun, + AutomationTrigger, +) + + engine = create_async_engine( DATABASE_URL, pool_size=30, From 56b3e1bfc4961493e4ad774c1ad25997e3171ae7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:48:41 +0200 Subject: [PATCH 034/133] refactor(automation): drop Block suffix from definition components --- .../app/automations/schemas/__init__.py | 12 ++++++------ .../app/automations/schemas/definition/__init__.py | 14 +++++++------- .../app/automations/schemas/definition/envelope.py | 12 ++++++------ .../automations/schemas/definition/execution.py | 4 ++-- .../app/automations/schemas/definition/inputs.py | 4 ++-- .../app/automations/schemas/definition/metadata.py | 4 ++-- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 2bb0060ba..8659ac9c9 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -5,9 +5,9 @@ from __future__ import annotations from .actions import AgentTaskActionParams from .definition import ( AutomationDefinition, - ExecutionBlock, - InputsBlock, - MetadataBlock, + Execution, + Inputs, + Metadata, PlanStep, TriggerSpec, ) @@ -16,10 +16,10 @@ from .triggers import ManualTriggerParams, ScheduleTriggerParams __all__ = [ "AgentTaskActionParams", "AutomationDefinition", - "ExecutionBlock", - "InputsBlock", + "Execution", + "Inputs", "ManualTriggerParams", - "MetadataBlock", + "Metadata", "PlanStep", "ScheduleTriggerParams", "TriggerSpec", diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 838e72f86..3fb0a739b 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -1,19 +1,19 @@ -"""Automation definition envelope and its building blocks.""" +"""Automation definition envelope and its components.""" from __future__ import annotations from .envelope import AutomationDefinition -from .execution import ExecutionBlock -from .inputs import InputsBlock -from .metadata import MetadataBlock +from .execution import Execution +from .inputs import Inputs +from .metadata import Metadata from .plan_step import PlanStep from .trigger_spec import TriggerSpec __all__ = [ "AutomationDefinition", - "ExecutionBlock", - "InputsBlock", - "MetadataBlock", + "Execution", + "Inputs", + "Metadata", "PlanStep", "TriggerSpec", ] diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index ffc45a0cd..f919b2abb 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -4,9 +4,9 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -from .execution import ExecutionBlock -from .inputs import InputsBlock -from .metadata import MetadataBlock +from .execution import Execution +from .inputs import Inputs +from .metadata import Metadata from .plan_step import PlanStep from .trigger_spec import TriggerSpec @@ -19,8 +19,8 @@ class AutomationDefinition(BaseModel): schema_version: str = "1.0" name: str = Field(..., min_length=1, max_length=200) goal: str | None = None - inputs: InputsBlock | None = None + inputs: Inputs | None = None triggers: list[TriggerSpec] = Field(default_factory=list) plan: list[PlanStep] = Field(..., min_length=1) - execution: ExecutionBlock = Field(default_factory=ExecutionBlock) - metadata: MetadataBlock = Field(default_factory=MetadataBlock) + execution: Execution = Field(default_factory=Execution) + metadata: Metadata = Field(default_factory=Metadata) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index d5f31364c..61861f8d8 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -1,4 +1,4 @@ -"""``ExecutionBlock`` — automation-wide execution defaults (overridable per step).""" +"""``Execution`` — automation-wide execution defaults (overridable per step).""" from __future__ import annotations @@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict, Field from .plan_step import PlanStep -class ExecutionBlock(BaseModel): +class Execution(BaseModel): model_config = ConfigDict(extra="forbid") timeout_seconds: int = Field(default=600, gt=0, description="Wall-clock cap for the run.") diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py index b0b1a9414..619fd16cd 100644 --- a/surfsense_backend/app/automations/schemas/definition/inputs.py +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -1,4 +1,4 @@ -"""``InputsBlock`` — JSON Schema for inputs an automation accepts at fire time.""" +"""``Inputs`` — JSON Schema for inputs an automation accepts at fire time.""" from __future__ import annotations @@ -7,7 +7,7 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -class InputsBlock(BaseModel): +class Inputs(BaseModel): model_config = ConfigDict( extra="forbid", populate_by_name=True, diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py index 9b3722430..3ac341d2e 100644 --- a/surfsense_backend/app/automations/schemas/definition/metadata.py +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -1,11 +1,11 @@ -"""``MetadataBlock`` — free-form metadata on a definition. Extra keys allowed.""" +"""``Metadata`` — free-form metadata on a definition. Extra keys allowed.""" from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -class MetadataBlock(BaseModel): +class Metadata(BaseModel): model_config = ConfigDict(extra="allow") tags: list[str] = Field(default_factory=list) From 99fd1a1338d870316e24d727380a9b1e46169e0a Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:58:57 +0200 Subject: [PATCH 035/133] feat(automation): register agent_task action and schedule/manual triggers --- .../registries/actions/__init__.py | 3 +++ .../registries/actions/agent_task.py | 27 +++++++++++++++++++ .../registries/triggers/__init__.py | 3 +++ .../automations/registries/triggers/manual.py | 17 ++++++++++++ .../registries/triggers/schedule.py | 21 +++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 surfsense_backend/app/automations/registries/actions/agent_task.py create mode 100644 surfsense_backend/app/automations/registries/triggers/manual.py create mode 100644 surfsense_backend/app/automations/registries/triggers/schedule.py diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index 1bb3ae9cc..68e507133 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -12,3 +12,6 @@ __all__ = [ "get_action", "register_action", ] + +# Built-in actions self-register at import time. +from . import agent_task # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/registries/actions/agent_task.py new file mode 100644 index 000000000..9acc11c2c --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/agent_task.py @@ -0,0 +1,27 @@ +"""Built-in ``agent_task`` action. Self-registers at import time.""" + +from __future__ import annotations + +from typing import Any + +from app.automations.schemas.actions import AgentTaskActionParams + +from .store import register_action +from .types import ActionDefinition + + +async def _handle_agent_task(args: dict[str, Any]) -> dict[str, Any]: + """Stub. Validates params; real wiring lands with the executor.""" + AgentTaskActionParams.model_validate(args) + return {"status": "stubbed"} + + +AGENT_TASK_ACTION = ActionDefinition( + type="agent_task", + name="Agent task", + description="Run an agent task with a scoped tool allowlist.", + params_schema=AgentTaskActionParams.model_json_schema(), + handler=_handle_agent_task, +) + +register_action(AGENT_TASK_ACTION) diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py index 843da5e70..e08dcce76 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -11,3 +11,6 @@ __all__ = [ "get_trigger", "register_trigger", ] + +# Built-in triggers self-register at import time. +from . import manual, schedule # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/triggers/manual.py b/surfsense_backend/app/automations/registries/triggers/manual.py new file mode 100644 index 000000000..173c38655 --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/manual.py @@ -0,0 +1,17 @@ +"""Built-in ``manual`` trigger. Self-registers at import time.""" + +from __future__ import annotations + +from app.automations.schemas.triggers import ManualTriggerParams + +from .store import register_trigger +from .types import TriggerDefinition + +MANUAL_TRIGGER = TriggerDefinition( + type="manual", + description="Fire on a user-initiated 'Run now' invocation.", + params_schema=ManualTriggerParams.model_json_schema(), + payload_schema={"type": "object"}, +) + +register_trigger(MANUAL_TRIGGER) diff --git a/surfsense_backend/app/automations/registries/triggers/schedule.py b/surfsense_backend/app/automations/registries/triggers/schedule.py new file mode 100644 index 000000000..0a6575f39 --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/schedule.py @@ -0,0 +1,21 @@ +"""Built-in ``schedule`` trigger. Self-registers at import time.""" + +from __future__ import annotations + +from app.automations.schemas.triggers import ScheduleTriggerParams + +from .store import register_trigger +from .types import TriggerDefinition + +SCHEDULE_TRIGGER = TriggerDefinition( + type="schedule", + description="Fire on a cron schedule in a given timezone.", + params_schema=ScheduleTriggerParams.model_json_schema(), + payload_schema={ + "type": "object", + "additionalProperties": False, + "properties": {}, + }, +) + +register_trigger(SCHEDULE_TRIGGER) From b4e5bf95a46a0f9025403be1c587f2822d18030e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 036/133] feat(automation): add template filter and test allowlist --- .../app/automations/templating/allowlist.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/allowlist.py diff --git a/surfsense_backend/app/automations/templating/allowlist.py b/surfsense_backend/app/automations/templating/allowlist.py new file mode 100644 index 000000000..ed0103c8f --- /dev/null +++ b/surfsense_backend/app/automations/templating/allowlist.py @@ -0,0 +1,31 @@ +"""Filter and test names admitted into the sandboxed environment.""" + +from __future__ import annotations + +ALLOWED_FILTERS: tuple[str, ...] = ( + "default", + "first", + "join", + "last", + "length", + "lower", + "replace", + "reverse", + "sort", + "tojson", + "trim", + "truncate", + "upper", + "date", + "slugify", +) + +ALLOWED_TESTS: tuple[str, ...] = ( + "defined", + "none", + "number", + "string", + "mapping", + "sequence", + "boolean", +) From 08e94ac5ca4056709220bfb9808b43966608610d Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 037/133] feat(automation): add custom template filters --- .../app/automations/templating/filters.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/filters.py diff --git a/surfsense_backend/app/automations/templating/filters.py b/surfsense_backend/app/automations/templating/filters.py new file mode 100644 index 000000000..65f66eb37 --- /dev/null +++ b/surfsense_backend/app/automations/templating/filters.py @@ -0,0 +1,29 @@ +"""Custom Jinja filters registered into the sandboxed environment.""" + +from __future__ import annotations + +import re +from typing import Any + + +def filter_date(value: Any, fmt: str = "%Y-%m-%d") -> str: + """Format a datetime-like value with ``strftime``. Strings pass through.""" + if value is None: + return "" + if isinstance(value, str): + return value + if hasattr(value, "strftime"): + return value.strftime(fmt) + raise ValueError(f"date filter requires datetime-like, got {type(value).__name__}") + + +_SLUG_NONALNUM = re.compile(r"[^a-z0-9]+") +_SLUG_DASHES = re.compile(r"-+") + + +def filter_slugify(value: Any) -> str: + """Lowercase, replace non-alphanumerics with hyphens, collapse and trim.""" + s = str(value).lower() + s = _SLUG_NONALNUM.sub("-", s) + s = _SLUG_DASHES.sub("-", s) + return s.strip("-") From 8345e79f6d71c1b7355038c1e1302bd51f042fec Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 038/133] feat(automation): add sandboxed template environment --- .../app/automations/templating/environment.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/environment.py diff --git a/surfsense_backend/app/automations/templating/environment.py b/surfsense_backend/app/automations/templating/environment.py new file mode 100644 index 000000000..6ac5f7361 --- /dev/null +++ b/surfsense_backend/app/automations/templating/environment.py @@ -0,0 +1,43 @@ +"""SandboxedEnvironment construction with the audited filter/test allowlist.""" + +from __future__ import annotations + +import json +from datetime import datetime +from typing import Any + +from jinja2 import StrictUndefined +from jinja2.sandbox import SandboxedEnvironment + +from .allowlist import ALLOWED_FILTERS, ALLOWED_TESTS +from .filters import filter_date, filter_slugify + + +def _finalize(value: Any) -> Any: + """Stringify common non-string values at output sites.""" + if value is None: + return "" + if isinstance(value, str): + return value + if isinstance(value, datetime): + return value.isoformat() + if isinstance(value, list | dict): + return json.dumps(value, ensure_ascii=False, default=str) + return value + + +def _build_env() -> SandboxedEnvironment: + env = SandboxedEnvironment( + autoescape=False, + undefined=StrictUndefined, + finalize=_finalize, + ) + env.globals.clear() + env.filters = {k: v for k, v in env.filters.items() if k in ALLOWED_FILTERS} + env.filters["date"] = filter_date + env.filters["slugify"] = filter_slugify + env.tests = {k: v for k, v in env.tests.items() if k in ALLOWED_TESTS} + return env + + +ENV: SandboxedEnvironment = _build_env() From de6da1b775708edd11e370e90d786604162527f4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 039/133] feat(automation): add template render and predicate evaluation --- .../app/automations/templating/render.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/render.py diff --git a/surfsense_backend/app/automations/templating/render.py b/surfsense_backend/app/automations/templating/render.py new file mode 100644 index 000000000..374095cd2 --- /dev/null +++ b/surfsense_backend/app/automations/templating/render.py @@ -0,0 +1,18 @@ +"""Render templates and evaluate predicates against the sandboxed environment.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from .environment import ENV + + +def render_template(template: str, context: Mapping[str, Any]) -> str: + """Render ``template`` with ``context``.""" + return ENV.from_string(template).render(**context) + + +def evaluate_predicate(expression: str, context: Mapping[str, Any]) -> bool: + """Evaluate a Jinja expression (not a template body) and coerce to bool.""" + return bool(ENV.compile_expression(expression)(**context)) From cb42b3a84f7b03ab611b233cbfd3c67f6e9cf67c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:18 +0200 Subject: [PATCH 040/133] feat(automation): add template run context builder --- .../app/automations/templating/__init__.py | 12 ++++++ .../app/automations/templating/context.py | 41 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/__init__.py create mode 100644 surfsense_backend/app/automations/templating/context.py diff --git a/surfsense_backend/app/automations/templating/__init__.py b/surfsense_backend/app/automations/templating/__init__.py new file mode 100644 index 000000000..8a00ec5ff --- /dev/null +++ b/surfsense_backend/app/automations/templating/__init__.py @@ -0,0 +1,12 @@ +"""Sandboxed template engine for automation definitions.""" + +from __future__ import annotations + +from .context import build_run_context +from .render import evaluate_predicate, render_template + +__all__ = [ + "build_run_context", + "evaluate_predicate", + "render_template", +] diff --git a/surfsense_backend/app/automations/templating/context.py b/surfsense_backend/app/automations/templating/context.py new file mode 100644 index 000000000..3ca87694c --- /dev/null +++ b/surfsense_backend/app/automations/templating/context.py @@ -0,0 +1,41 @@ +"""Builder for the ``{run, inputs, steps}`` namespace exposed to every template.""" + +from __future__ import annotations + +from collections.abc import Mapping +from datetime import datetime +from typing import Any + + +def build_run_context( + *, + run_id: int, + automation_id: int, + automation_name: str | None, + automation_version: int | None, + search_space_id: int | None, + creator_id: Any, + trigger_id: int | None, + trigger_type: str | None, + started_at: datetime | None, + attempt: int, + resolved_inputs: Mapping[str, Any], + step_outputs: Mapping[str, Any], +) -> dict[str, Any]: + """Build the ``{run, inputs, steps}`` namespace exposed to every template.""" + return { + "run": { + "id": run_id, + "automation_id": automation_id, + "automation_name": automation_name, + "automation_version": automation_version, + "search_space_id": search_space_id, + "creator_id": creator_id, + "trigger_id": trigger_id, + "trigger_type": trigger_type, + "started_at": started_at, + "attempt": attempt, + }, + "inputs": dict(resolved_inputs), + "steps": dict(step_outputs), + } From 8b87d179e92eb52b247e3e910074b15d6f54ade9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 041/133] feat(automation): add recursive render_value to templating --- .../app/automations/templating/__init__.py | 3 ++- .../app/automations/templating/render.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/surfsense_backend/app/automations/templating/__init__.py b/surfsense_backend/app/automations/templating/__init__.py index 8a00ec5ff..1df1809c7 100644 --- a/surfsense_backend/app/automations/templating/__init__.py +++ b/surfsense_backend/app/automations/templating/__init__.py @@ -3,10 +3,11 @@ from __future__ import annotations from .context import build_run_context -from .render import evaluate_predicate, render_template +from .render import evaluate_predicate, render_template, render_value __all__ = [ "build_run_context", "evaluate_predicate", "render_template", + "render_value", ] diff --git a/surfsense_backend/app/automations/templating/render.py b/surfsense_backend/app/automations/templating/render.py index 374095cd2..42721ddeb 100644 --- a/surfsense_backend/app/automations/templating/render.py +++ b/surfsense_backend/app/automations/templating/render.py @@ -16,3 +16,14 @@ def render_template(template: str, context: Mapping[str, Any]) -> str: def evaluate_predicate(expression: str, context: Mapping[str, Any]) -> bool: """Evaluate a Jinja expression (not a template body) and coerce to bool.""" return bool(ENV.compile_expression(expression)(**context)) + + +def render_value(value: Any, context: Mapping[str, Any]) -> Any: + """Recursively render every string in a JSON-like value structure.""" + if isinstance(value, str): + return render_template(value, context) + if isinstance(value, dict): + return {k: render_value(v, context) for k, v in value.items()} + if isinstance(value, list): + return [render_value(v, context) for v in value] + return value From 924a82c0b1b92e7bca10a2f4e7f5dfd5e39b7fcc Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 042/133] feat(automation): add retry policy helper --- .../app/automations/runtime/retries.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/retries.py diff --git a/surfsense_backend/app/automations/runtime/retries.py b/surfsense_backend/app/automations/runtime/retries.py new file mode 100644 index 000000000..d5bfb15ca --- /dev/null +++ b/surfsense_backend/app/automations/runtime/retries.py @@ -0,0 +1,36 @@ +"""Retry policy enforcement for action handlers.""" + +from __future__ import annotations + +import asyncio +from collections.abc import Awaitable, Callable + + +async def with_retries[T]( + coro_factory: Callable[[], Awaitable[T]], + *, + max_retries: int, + backoff: str, + timeout: int | None, +) -> tuple[T, int]: + """Call ``coro_factory`` up to ``1 + max_retries`` times. Return ``(result, attempts)``.""" + total = 1 + max(0, max_retries) + for attempt in range(1, total + 1): + try: + coro = coro_factory() + if timeout is not None and timeout > 0: + return await asyncio.wait_for(coro, timeout=timeout), attempt + return await coro, attempt + except Exception: + if attempt >= total: + raise + await asyncio.sleep(_backoff_seconds(backoff, attempt)) + raise RuntimeError("with_retries exhausted without raising or returning") + + +def _backoff_seconds(strategy: str, attempt: int) -> float: + if strategy == "exponential": + return float(2 ** (attempt - 1)) + if strategy == "linear": + return float(attempt) + return 0.0 From f71a02db2f27fe56467794a0f53a6d331414a273 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 043/133] feat(automation): add automation run repository --- .../app/automations/runtime/repository.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/repository.py diff --git a/surfsense_backend/app/automations/runtime/repository.py b/surfsense_backend/app/automations/runtime/repository.py new file mode 100644 index 000000000..a8bdbc55a --- /dev/null +++ b/surfsense_backend/app/automations/runtime/repository.py @@ -0,0 +1,62 @@ +"""Persistence operations on ``AutomationRun``. Pure SQL, no business logic.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import Any + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload + +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.models.run import AutomationRun + + +async def load_run(session: AsyncSession, run_id: int) -> AutomationRun | None: + """Load a run with its automation and trigger eagerly loaded.""" + stmt = ( + select(AutomationRun) + .where(AutomationRun.id == run_id) + .options( + selectinload(AutomationRun.automation), + selectinload(AutomationRun.trigger), + ) + ) + result = await session.execute(stmt) + return result.scalar_one_or_none() + + +async def mark_running(session: AsyncSession, run: AutomationRun) -> None: + run.status = RunStatus.RUNNING + run.started_at = datetime.now(UTC) + await session.flush() + + +async def mark_succeeded(session: AsyncSession, run: AutomationRun) -> None: + run.status = RunStatus.SUCCEEDED + run.finished_at = datetime.now(UTC) + await session.flush() + + +async def mark_failed( + session: AsyncSession, + run: AutomationRun, + error: dict[str, Any] | None, +) -> None: + run.status = RunStatus.FAILED + run.finished_at = datetime.now(UTC) + run.error = error + await session.flush() + + +async def append_step_result( + session: AsyncSession, + run: AutomationRun, + step_result: dict[str, Any], +) -> None: + """Append one step result. Reassigns the list so SQLAlchemy detects the change.""" + current = list(run.step_results or []) + current.append(step_result) + run.step_results = current + await session.flush() From 0a329e5a69bd2604471730f9108a4fa9ee8d5a13 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 044/133] feat(automation): add per-step execution --- .../app/automations/runtime/step.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/step.py diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py new file mode 100644 index 000000000..07b894a91 --- /dev/null +++ b/surfsense_backend/app/automations/runtime/step.py @@ -0,0 +1,92 @@ +"""Execute one plan step: when-predicate, params render, handler dispatch, retries.""" + +from __future__ import annotations + +from collections.abc import Mapping +from datetime import UTC, datetime +from typing import Any + +from app.automations.registries import get_action +from app.automations.schemas.definition.plan_step import PlanStep +from app.automations.templating import evaluate_predicate, render_value + +from .retries import with_retries + + +async def execute_step( + *, + step: PlanStep, + template_context: Mapping[str, Any], + default_max_retries: int, + default_retry_backoff: str, + default_timeout_seconds: int, +) -> dict[str, Any]: + """Run one step and return its structured result entry.""" + started_at = datetime.now(UTC) + + if step.when is not None: + try: + should_run = evaluate_predicate(step.when, template_context) + except Exception as exc: + return _result(step, "failed", started_at, attempts=0, error=_error(exc, "when")) + if not should_run: + return _result(step, "skipped", started_at, attempts=0) + + try: + resolved_params = render_value(step.params, template_context) + except Exception as exc: + return _result(step, "failed", started_at, attempts=0, error=_error(exc, "render")) + + action = get_action(step.action) + if action is None: + return _result( + step, + "failed", + started_at, + attempts=0, + error={"message": f"action not registered: {step.action}", "type": "ActionNotFound"}, + ) + + max_retries = step.max_retries if step.max_retries is not None else default_max_retries + timeout = step.timeout_seconds or default_timeout_seconds + + try: + result, attempts = await with_retries( + lambda: action.handler(resolved_params), + max_retries=max_retries, + backoff=default_retry_backoff, + timeout=timeout, + ) + except Exception as exc: + return _result(step, "failed", started_at, attempts=max_retries + 1, error=_error(exc)) + + return _result(step, "succeeded", started_at, attempts=attempts, result=result) + + +def _result( + step: PlanStep, + status: str, + started_at: datetime, + *, + attempts: int, + result: Any = None, + error: dict[str, Any] | None = None, +) -> dict[str, Any]: + entry: dict[str, Any] = { + "step_id": step.step_id, + "action": step.action, + "status": status, + "started_at": started_at.isoformat(), + "finished_at": datetime.now(UTC).isoformat(), + "attempts": attempts, + } + if result is not None: + entry["result"] = result + if error is not None: + entry["error"] = error + return entry + + +def _error(exc: Exception, phase: str | None = None) -> dict[str, Any]: + msg = f"{phase}: {exc}" if phase else str(exc) + return {"message": msg, "type": type(exc).__name__} From d3cda121917268f198a927ebfcff633c2b4cd8e3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 045/133] feat(automation): add automation run executor --- .../app/automations/runtime/executor.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/executor.py diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py new file mode 100644 index 000000000..51c4417e3 --- /dev/null +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -0,0 +1,105 @@ +"""Walk an ``AutomationRun``'s snapshot plan to terminal state.""" + +from __future__ import annotations + +from typing import Any + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.models.run import AutomationRun +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.templating import build_run_context + +from . import repository +from .step import execute_step + + +async def execute_run(session: AsyncSession, run_id: int) -> None: + """Load run ``run_id`` and execute its snapshot plan to a terminal state.""" + run = await repository.load_run(session, run_id) + if run is None: + raise ValueError(f"automation_run {run_id} not found") + + if run.status != RunStatus.PENDING: + return + + try: + definition = AutomationDefinition.model_validate(run.definition_snapshot) + except Exception as exc: + await repository.mark_failed( + session, + run, + {"message": f"definition_snapshot invalid: {exc}", "type": type(exc).__name__}, + ) + await session.commit() + return + + await repository.mark_running(session, run) + await session.commit() + + step_outputs: dict[str, Any] = {} + + for step in definition.plan: + ctx = _build_ctx(run, step_outputs) + result = await execute_step( + step=step, + template_context=ctx, + default_max_retries=definition.execution.max_retries, + default_retry_backoff=definition.execution.retry_backoff, + default_timeout_seconds=definition.execution.timeout_seconds, + ) + await repository.append_step_result(session, run, result) + await session.commit() + + if result["status"] == "failed": + await _run_on_failure(session, run, definition) + await repository.mark_failed(session, run, result.get("error")) + await session.commit() + return + + if result["status"] == "succeeded": + step_outputs[step.output_as or step.step_id] = result.get("result") + + await repository.mark_succeeded(session, run) + await session.commit() + + +async def _run_on_failure( + session: AsyncSession, + run: AutomationRun, + definition: AutomationDefinition, +) -> None: + """Run the on_failure steps. Their failures don't recurse into more on_failure.""" + if not definition.execution.on_failure: + return + ctx = _build_ctx(run, step_outputs={}) + for step in definition.execution.on_failure: + result = await execute_step( + step=step, + template_context=ctx, + default_max_retries=definition.execution.max_retries, + default_retry_backoff=definition.execution.retry_backoff, + default_timeout_seconds=definition.execution.timeout_seconds, + ) + await repository.append_step_result(session, run, result) + await session.commit() + + +def _build_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: + automation = run.automation + trigger = run.trigger + return build_run_context( + run_id=run.id, + automation_id=run.automation_id, + automation_name=automation.name if automation else None, + automation_version=automation.version if automation else None, + search_space_id=automation.search_space_id if automation else None, + creator_id=automation.created_by_user_id if automation else None, + trigger_id=run.trigger_id, + trigger_type=trigger.type.value if trigger else None, + started_at=run.started_at, + attempt=1, + resolved_inputs=run.resolved_inputs or {}, + step_outputs=step_outputs, + ) From 273b98f350981dcd348527573d0569ab8e192590 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 046/133] feat(automation): expose runtime package surface --- surfsense_backend/app/automations/runtime/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/__init__.py diff --git a/surfsense_backend/app/automations/runtime/__init__.py b/surfsense_backend/app/automations/runtime/__init__.py new file mode 100644 index 000000000..0650882b2 --- /dev/null +++ b/surfsense_backend/app/automations/runtime/__init__.py @@ -0,0 +1,7 @@ +"""Automation run executor: plan walker, step dispatch, retries, persistence.""" + +from __future__ import annotations + +from .executor import execute_run + +__all__ = ["execute_run"] From b26bf0bbcf0858839e188cacee4b7ace6a27b50c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 047/133] feat(automation): register automation run celery task --- .../app/automations/tasks/__init__.py | 3 ++ .../app/automations/tasks/execute_run.py | 33 +++++++++++++++++++ surfsense_backend/app/celery_app.py | 1 + 3 files changed, 37 insertions(+) create mode 100644 surfsense_backend/app/automations/tasks/__init__.py create mode 100644 surfsense_backend/app/automations/tasks/execute_run.py diff --git a/surfsense_backend/app/automations/tasks/__init__.py b/surfsense_backend/app/automations/tasks/__init__.py new file mode 100644 index 000000000..6fe0d62e8 --- /dev/null +++ b/surfsense_backend/app/automations/tasks/__init__.py @@ -0,0 +1,3 @@ +"""Celery task wrappers for the automation runtime.""" + +from __future__ import annotations diff --git a/surfsense_backend/app/automations/tasks/execute_run.py b/surfsense_backend/app/automations/tasks/execute_run.py new file mode 100644 index 000000000..5fc84698b --- /dev/null +++ b/surfsense_backend/app/automations/tasks/execute_run.py @@ -0,0 +1,33 @@ +"""Celery task that runs one automation. Thin wrapper over ``runtime.executor``.""" + +from __future__ import annotations + +import logging + +from app.automations.runtime import execute_run +from app.celery_app import celery_app +from app.tasks.celery_tasks import ( + get_celery_session_maker, + run_async_celery_task, +) + +logger = logging.getLogger(__name__) + +TASK_NAME = "automation_run_execute" + + +@celery_app.task(name=TASK_NAME, bind=True) +def automation_run_execute(self, run_id: int) -> None: # noqa: ARG001 — Celery bind + """Execute one ``AutomationRun``. Idempotent: terminal runs no-op.""" + return run_async_celery_task(lambda: _impl(run_id)) + + +async def _impl(run_id: int) -> None: + session_maker = get_celery_session_maker() + async with session_maker() as session: + try: + await execute_run(session, run_id) + except Exception: + logger.exception("automation_run %d failed unexpectedly", run_id) + await session.rollback() + raise diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index 5b45baca1..569178239 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -188,6 +188,7 @@ celery_app = Celery( "app.tasks.celery_tasks.document_reindex_tasks", "app.tasks.celery_tasks.stale_notification_cleanup_task", "app.tasks.celery_tasks.stripe_reconciliation_task", + "app.automations.tasks.execute_run", ], ) From 1366c8a711b5cfd17caaa3f328d130bc7038ad27 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:30:34 +0200 Subject: [PATCH 048/133] feat(rbac): add automations permission family --- surfsense_backend/app/db.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 71466495b..ac880ded5 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -439,6 +439,13 @@ class Permission(StrEnum): PUBLIC_SHARING_CREATE = "public_sharing:create" PUBLIC_SHARING_DELETE = "public_sharing:delete" + # Automations + AUTOMATIONS_CREATE = "automations:create" + AUTOMATIONS_READ = "automations:read" + AUTOMATIONS_UPDATE = "automations:update" + AUTOMATIONS_DELETE = "automations:delete" + AUTOMATIONS_EXECUTE = "automations:execute" + # Full access wildcard FULL_ACCESS = "*" @@ -494,6 +501,11 @@ DEFAULT_ROLE_PERMISSIONS = { # Public Sharing (can create and view, no delete) Permission.PUBLIC_SHARING_VIEW.value, Permission.PUBLIC_SHARING_CREATE.value, + # Automations (no delete) + Permission.AUTOMATIONS_CREATE.value, + Permission.AUTOMATIONS_READ.value, + Permission.AUTOMATIONS_UPDATE.value, + Permission.AUTOMATIONS_EXECUTE.value, ], "Viewer": [ # Documents (read only) @@ -525,6 +537,8 @@ DEFAULT_ROLE_PERMISSIONS = { Permission.SETTINGS_VIEW.value, # Public Sharing (view only) Permission.PUBLIC_SHARING_VIEW.value, + # Automations (read only) + Permission.AUTOMATIONS_READ.value, ], } From 3bb02d8889559d0cd46f661e7f51f576a39600c9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:30:41 +0200 Subject: [PATCH 049/133] feat(automations): add manual dispatch service --- .../app/automations/dispatch/__init__.py | 8 ++ .../app/automations/dispatch/manual.py | 107 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 surfsense_backend/app/automations/dispatch/__init__.py create mode 100644 surfsense_backend/app/automations/dispatch/manual.py diff --git a/surfsense_backend/app/automations/dispatch/__init__.py b/surfsense_backend/app/automations/dispatch/__init__.py new file mode 100644 index 000000000..4a549a4ce --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/__init__.py @@ -0,0 +1,8 @@ +"""Public dispatch surface for firing automations.""" + +from .manual import DispatchError, dispatch_manual_run + +__all__ = [ + "DispatchError", + "dispatch_manual_run", +] diff --git a/surfsense_backend/app/automations/dispatch/manual.py b/surfsense_backend/app/automations/dispatch/manual.py new file mode 100644 index 000000000..221d6a3e2 --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/manual.py @@ -0,0 +1,107 @@ +"""Manual ``Run now`` dispatch: validate inputs, snapshot the definition, enqueue.""" + +from __future__ import annotations + +from typing import Any + +import jsonschema +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.tasks.execute_run import automation_run_execute + + +class DispatchError(Exception): + """A manual dispatch could not proceed (missing trigger, invalid inputs, ...).""" + + +async def dispatch_manual_run( + *, + session: AsyncSession, + automation_id: int, + payload: dict[str, Any] | None, +) -> AutomationRun: + """Validate, snapshot, persist, and enqueue an ``AutomationRun``.""" + automation = await _load_automation(session, automation_id) + if automation is None: + raise DispatchError(f"automation {automation_id} not found") + + if automation.status != AutomationStatus.ACTIVE: + raise DispatchError( + f"automation {automation_id} is {automation.status.value}, not active" + ) + + try: + definition = AutomationDefinition.model_validate(automation.definition) + except Exception as exc: + raise DispatchError(f"invalid automation definition: {exc}") from exc + + trigger = await _find_manual_trigger(session, automation_id) + if trigger is None: + raise DispatchError( + f"automation {automation_id} has no enabled manual trigger" + ) + + resolved_inputs = _validate_inputs(definition, payload or {}) + snapshot = definition.model_dump(mode="json", by_alias=True) + + run = AutomationRun( + automation_id=automation_id, + trigger_id=trigger.id, + status=RunStatus.PENDING, + definition_snapshot=snapshot, + trigger_payload=payload, + resolved_inputs=resolved_inputs, + step_results=[], + artifacts=[], + ) + session.add(run) + await session.commit() + await session.refresh(run) + + automation_run_execute.apply_async( + args=[run.id], + time_limit=definition.execution.timeout_seconds, + ) + return run + + +async def _load_automation( + session: AsyncSession, automation_id: int +) -> Automation | None: + stmt = select(Automation).where(Automation.id == automation_id) + return (await session.execute(stmt)).scalar_one_or_none() + + +async def _find_manual_trigger( + session: AsyncSession, automation_id: int +) -> AutomationTrigger | None: + stmt = ( + select(AutomationTrigger) + .where( + AutomationTrigger.automation_id == automation_id, + AutomationTrigger.type == TriggerType.MANUAL, + AutomationTrigger.enabled.is_(True), + ) + .limit(1) + ) + return (await session.execute(stmt)).scalar_one_or_none() + + +def _validate_inputs( + definition: AutomationDefinition, payload: dict[str, Any] +) -> dict[str, Any]: + if definition.inputs is None or not definition.inputs.schema_: + return {} + try: + jsonschema.validate(instance=payload, schema=definition.inputs.schema_) + except jsonschema.ValidationError as exc: + raise DispatchError(f"inputs: {exc.message}") from exc + return payload From cfbe2a7fe025cbf476f3ba1ada33ef3dcdcdbd35 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:30:45 +0200 Subject: [PATCH 050/133] feat(automations): expose POST /automations/{id}/run --- surfsense_backend/app/routes/__init__.py | 2 + .../app/routes/automations_routes.py | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 surfsense_backend/app/routes/automations_routes.py diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index ec4d1650f..1d3ca2141 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -7,6 +7,7 @@ from .agent_revert_route import router as agent_revert_router from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) +from .automations_routes import router as automations_router from .chat_comments_routes import router as chat_comments_router from .circleback_webhook_route import router as circleback_webhook_router from .clickup_add_connector_route import router as clickup_add_connector_router @@ -119,3 +120,4 @@ router.include_router(youtube_router) # YouTube playlist resolution router.include_router(prompts_router) router.include_router(memory_router) # User personal memory (memory.md style) router.include_router(team_memory_router) # Search-space team memory +router.include_router(automations_router) # Automations (manual run-now) diff --git a/surfsense_backend/app/routes/automations_routes.py b/surfsense_backend/app/routes/automations_routes.py new file mode 100644 index 000000000..02c019625 --- /dev/null +++ b/surfsense_backend/app/routes/automations_routes.py @@ -0,0 +1,55 @@ +"""Routes for automations. v1: manual ``Run now``.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, Depends, HTTPException +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError, dispatch_manual_run +from app.automations.persistence.models.automation import Automation +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + +router = APIRouter() + + +@router.post("/automations/{automation_id}/run") +async def run_automation_now( + automation_id: int, + payload: dict[str, Any] | None = Body(default=None), + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> dict[str, Any]: + """Fire an automation manually. Returns the new run id and status.""" + search_space_id = ( + await session.execute( + select(Automation.search_space_id).where(Automation.id == automation_id) + ) + ).scalar_one_or_none() + if search_space_id is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + + await check_permission( + session, + user, + search_space_id, + Permission.AUTOMATIONS_EXECUTE.value, + "You don't have permission to execute automations in this search space", + ) + + try: + run = await dispatch_manual_run( + session=session, + automation_id=automation_id, + payload=payload, + ) + except DispatchError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + return {"run_id": run.id, "status": run.status.value} From f646b5cbab2d69c061b70caca8664e33dcaf379c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:37:25 +0200 Subject: [PATCH 051/133] feat(rbac): backfill automations permissions on existing roles --- ...45_add_automations_permissions_to_roles.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py diff --git a/surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py b/surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py new file mode 100644 index 000000000..779656b44 --- /dev/null +++ b/surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py @@ -0,0 +1,87 @@ +"""Add automations permissions to existing Editor/Viewer roles + +Revision ID: 145 +Revises: 144 +Create Date: 2026-05-27 + +Owners already have ``*`` and need no backfill. Custom (non-system) roles +are left untouched on purpose: workspace admins manage those explicitly. +""" + +from collections.abc import Sequence + +from sqlalchemy import text + +from alembic import op + +revision: str = "145" +down_revision: str | None = "144" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +_EDITOR_PERMISSIONS = ( + "automations:create", + "automations:read", + "automations:update", + "automations:execute", +) +_VIEWER_PERMISSIONS = ("automations:read",) + + +def upgrade(): + connection = op.get_bind() + + for permission in _EDITOR_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_append(permissions, :permission) + WHERE name = 'Editor' + AND NOT (:permission = ANY(permissions)) + """ + ), + {"permission": permission}, + ) + + for permission in _VIEWER_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_append(permissions, :permission) + WHERE name = 'Viewer' + AND NOT (:permission = ANY(permissions)) + """ + ), + {"permission": permission}, + ) + + +def downgrade(): + connection = op.get_bind() + + for permission in _EDITOR_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_remove(permissions, :permission) + WHERE name = 'Editor' + """ + ), + {"permission": permission}, + ) + + for permission in _VIEWER_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_remove(permissions, :permission) + WHERE name = 'Viewer' + """ + ), + {"permission": permission}, + ) From 7ec3468113fe28ffb7c634aee790bfd91c625766 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 16:29:32 +0200 Subject: [PATCH 052/133] refactor(automations): bind action handlers via ActionContext factory --- .../app/automations/registries/__init__.py | 4 +++ .../registries/actions/__init__.py | 4 ++- .../registries/actions/agent_task.py | 17 +++++++---- .../automations/registries/actions/types.py | 20 +++++++++++-- .../app/automations/runtime/executor.py | 29 +++++++++++++++---- .../app/automations/runtime/step.py | 6 +++- 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index f497caf59..f6af3817b 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -3,8 +3,10 @@ from __future__ import annotations from .actions import ( + ActionContext, ActionDefinition, ActionHandler, + ActionHandlerFactory, all_actions, get_action, register_action, @@ -17,8 +19,10 @@ from .triggers import ( ) __all__ = [ + "ActionContext", "ActionDefinition", "ActionHandler", + "ActionHandlerFactory", "TriggerDefinition", "all_actions", "all_triggers", diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index 68e507133..b95c634f2 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -3,11 +3,13 @@ from __future__ import annotations from .store import all_actions, get_action, register_action -from .types import ActionDefinition, ActionHandler +from .types import ActionContext, ActionDefinition, ActionHandler, ActionHandlerFactory __all__ = [ + "ActionContext", "ActionDefinition", "ActionHandler", + "ActionHandlerFactory", "all_actions", "get_action", "register_action", diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/registries/actions/agent_task.py index 9acc11c2c..beba455cc 100644 --- a/surfsense_backend/app/automations/registries/actions/agent_task.py +++ b/surfsense_backend/app/automations/registries/actions/agent_task.py @@ -7,13 +7,18 @@ from typing import Any from app.automations.schemas.actions import AgentTaskActionParams from .store import register_action -from .types import ActionDefinition +from .types import ActionContext, ActionDefinition, ActionHandler -async def _handle_agent_task(args: dict[str, Any]) -> dict[str, Any]: - """Stub. Validates params; real wiring lands with the executor.""" - AgentTaskActionParams.model_validate(args) - return {"status": "stubbed"} +def _build_handler(ctx: ActionContext) -> ActionHandler: + """Bind run/session context to the agent_task handler. Real wiring lands in Phase 4b.""" + del ctx # ignored by the stub; real handler will consume it + + async def handle(params: dict[str, Any]) -> dict[str, Any]: + AgentTaskActionParams.model_validate(params) + return {"status": "stubbed"} + + return handle AGENT_TASK_ACTION = ActionDefinition( @@ -21,7 +26,7 @@ AGENT_TASK_ACTION = ActionDefinition( name="Agent task", description="Run an agent task with a scoped tool allowlist.", params_schema=AgentTaskActionParams.model_json_schema(), - handler=_handle_agent_task, + build_handler=_build_handler, ) register_action(AGENT_TASK_ACTION) diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py index 99f94ae7c..433c60841 100644 --- a/surfsense_backend/app/automations/registries/actions/types.py +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -1,12 +1,28 @@ -"""``ActionDefinition`` dataclass and handler signature.""" +"""``ActionDefinition``, ``ActionContext``, and handler/factory signatures.""" from __future__ import annotations from collections.abc import Awaitable, Callable from dataclasses import dataclass from typing import Any +from uuid import UUID + +from sqlalchemy.ext.asyncio import AsyncSession + + +@dataclass(frozen=True, slots=True) +class ActionContext: + """Per-invocation dependencies bound to an action handler at execute time.""" + + session: AsyncSession + run_id: int + step_id: str + search_space_id: int + creator_user_id: UUID | None + ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] +ActionHandlerFactory = Callable[[ActionContext], ActionHandler] @dataclass(frozen=True, slots=True) @@ -15,4 +31,4 @@ class ActionDefinition: name: str description: str params_schema: dict[str, Any] - handler: ActionHandler + build_handler: ActionHandlerFactory diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index 51c4417e3..e9e55b02d 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -8,7 +8,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun +from app.automations.registries.actions.types import ActionContext from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context from . import repository @@ -41,10 +43,12 @@ async def execute_run(session: AsyncSession, run_id: int) -> None: step_outputs: dict[str, Any] = {} for step in definition.plan: - ctx = _build_ctx(run, step_outputs) + template_ctx = _build_template_ctx(run, step_outputs) + action_ctx = _build_action_ctx(session, run, step) result = await execute_step( step=step, - template_context=ctx, + template_context=template_ctx, + action_context=action_ctx, default_max_retries=definition.execution.max_retries, default_retry_backoff=definition.execution.retry_backoff, default_timeout_seconds=definition.execution.timeout_seconds, @@ -73,11 +77,13 @@ async def _run_on_failure( """Run the on_failure steps. Their failures don't recurse into more on_failure.""" if not definition.execution.on_failure: return - ctx = _build_ctx(run, step_outputs={}) + template_ctx = _build_template_ctx(run, step_outputs={}) for step in definition.execution.on_failure: + action_ctx = _build_action_ctx(session, run, step) result = await execute_step( step=step, - template_context=ctx, + template_context=template_ctx, + action_context=action_ctx, default_max_retries=definition.execution.max_retries, default_retry_backoff=definition.execution.retry_backoff, default_timeout_seconds=definition.execution.timeout_seconds, @@ -86,7 +92,7 @@ async def _run_on_failure( await session.commit() -def _build_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: +def _build_template_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: automation = run.automation trigger = run.trigger return build_run_context( @@ -103,3 +109,16 @@ def _build_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, An resolved_inputs=run.resolved_inputs or {}, step_outputs=step_outputs, ) + + +def _build_action_ctx( + session: AsyncSession, run: AutomationRun, step: PlanStep +) -> ActionContext: + automation = run.automation + return ActionContext( + session=session, + run_id=run.id, + step_id=step.step_id, + search_space_id=automation.search_space_id, + creator_user_id=automation.created_by_user_id, + ) diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py index 07b894a91..76e3ba171 100644 --- a/surfsense_backend/app/automations/runtime/step.py +++ b/surfsense_backend/app/automations/runtime/step.py @@ -7,6 +7,7 @@ from datetime import UTC, datetime from typing import Any from app.automations.registries import get_action +from app.automations.registries.actions.types import ActionContext from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import evaluate_predicate, render_value @@ -17,6 +18,7 @@ async def execute_step( *, step: PlanStep, template_context: Mapping[str, Any], + action_context: ActionContext, default_max_retries: int, default_retry_backoff: str, default_timeout_seconds: int, @@ -47,12 +49,14 @@ async def execute_step( error={"message": f"action not registered: {step.action}", "type": "ActionNotFound"}, ) + handler = action.build_handler(action_context) + max_retries = step.max_retries if step.max_retries is not None else default_max_retries timeout = step.timeout_seconds or default_timeout_seconds try: result, attempts = await with_retries( - lambda: action.handler(resolved_params), + lambda: handler(resolved_params), max_retries=max_retries, backoff=default_retry_backoff, timeout=timeout, From ce45e110096aa560f5e53393a911d07fdffd0d30 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:02:44 +0200 Subject: [PATCH 053/133] feat(automations): wire agent_task to multi_agent_chat with auto-approve loop --- .../app/automations/actions/__init__.py | 1 + .../actions/agent_task/__init__.py | 7 ++ .../actions/agent_task/auto_decide.py | 39 ++++++++ .../actions/agent_task/dependencies.py | 58 +++++++++++ .../automations/actions/agent_task/factory.py | 27 ++++++ .../actions/agent_task/finalize.py | 44 +++++++++ .../automations/actions/agent_task/invoke.py | 97 +++++++++++++++++++ .../registries/actions/agent_task.py | 21 +--- .../automations/schemas/actions/agent_task.py | 22 ++--- 9 files changed, 285 insertions(+), 31 deletions(-) create mode 100644 surfsense_backend/app/automations/actions/__init__.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/__init__.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/auto_decide.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/dependencies.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/factory.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/finalize.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/invoke.py diff --git a/surfsense_backend/app/automations/actions/__init__.py b/surfsense_backend/app/automations/actions/__init__.py new file mode 100644 index 000000000..2a518c1db --- /dev/null +++ b/surfsense_backend/app/automations/actions/__init__.py @@ -0,0 +1 @@ +"""Action implementations. One subpackage per built-in action type.""" diff --git a/surfsense_backend/app/automations/actions/agent_task/__init__.py b/surfsense_backend/app/automations/actions/agent_task/__init__.py new file mode 100644 index 000000000..ecf79b448 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/__init__.py @@ -0,0 +1,7 @@ +"""``agent_task`` action: spin up multi_agent_chat for one rendered query.""" + +from __future__ import annotations + +from .factory import build_handler + +__all__ = ["build_handler"] diff --git a/surfsense_backend/app/automations/actions/agent_task/auto_decide.py b/surfsense_backend/app/automations/actions/agent_task/auto_decide.py new file mode 100644 index 000000000..357eeb565 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/auto_decide.py @@ -0,0 +1,39 @@ +"""Synthesize HITL decisions for every pending interrupt (approve-all or reject-all).""" + +from __future__ import annotations + +from typing import Any + + +def build_auto_decisions( + state: Any, decision: str +) -> tuple[dict[str, dict[str, Any]], dict[str, dict[str, Any]]]: + """Return ``(lg_resume_map, surfsense_resume_value)`` covering every pending interrupt. + + ``lg_resume_map`` is keyed by ``Interrupt.id`` for ``Command(resume=...)``; + ``surfsense_resume_value`` is keyed by ``tool_call_id`` for the subagent + middleware bridge. Action count is read from ``value.action_requests`` when + present and falls back to ``1`` for wrapped scalar interrupts. + """ + lg_resume_map: dict[str, dict[str, Any]] = {} + routed: dict[str, dict[str, Any]] = {} + + for interrupt_obj in getattr(state, "interrupts", ()) or (): + value = getattr(interrupt_obj, "value", None) + if not isinstance(value, dict): + continue + interrupt_id = getattr(interrupt_obj, "id", None) + if not isinstance(interrupt_id, str): + continue + + action_requests = value.get("action_requests") + count = len(action_requests) if isinstance(action_requests, list) else 1 + decisions = [{"type": decision} for _ in range(count)] + + lg_resume_map[interrupt_id] = {"decisions": decisions} + + tool_call_id = value.get("tool_call_id") + if isinstance(tool_call_id, str): + routed[tool_call_id] = {"decisions": decisions} + + return lg_resume_map, routed diff --git a/surfsense_backend/app/automations/actions/agent_task/dependencies.py b/surfsense_backend/app/automations/actions/agent_task/dependencies.py new file mode 100644 index 000000000..12273aa0f --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/dependencies.py @@ -0,0 +1,58 @@ +"""Build the per-invocation dependencies the multi_agent_chat factory needs.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( + get_chat_checkpointer, + setup_connector_and_firecrawl, +) + + +class DependencyError(Exception): + """An external dependency (LLM config, checkpointer, ...) refused to load.""" + + +@dataclass(frozen=True, slots=True) +class AgentDependencies: + """Everything ``create_multi_agent_chat_deep_agent`` needs from the environment.""" + + llm: Any + agent_config: Any + connector_service: Any + firecrawl_api_key: str | None + checkpointer: Any + + +async def build_dependencies( + *, + session: AsyncSession, + search_space_id: int, +) -> AgentDependencies: + """Load the LLM bundle, connector service, and checkpointer for one invoke. + + Uses the search space's default LLM config (``config_id=-1``). Per-step + model overrides land in a future iteration alongside the ``model`` param. + """ + llm, agent_config, err = await load_llm_bundle( + session, config_id=-1, search_space_id=search_space_id + ) + if err is not None or llm is None: + raise DependencyError(err or "failed to load default LLM config") + + connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( + session, search_space_id=search_space_id + ) + checkpointer = await get_chat_checkpointer() + return AgentDependencies( + llm=llm, + agent_config=agent_config, + connector_service=connector_service, + firecrawl_api_key=firecrawl_api_key, + checkpointer=checkpointer, + ) diff --git a/surfsense_backend/app/automations/actions/agent_task/factory.py b/surfsense_backend/app/automations/actions/agent_task/factory.py new file mode 100644 index 000000000..a0d867f38 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/factory.py @@ -0,0 +1,27 @@ +"""Bind ``ActionContext`` to a callable that runs one ``agent_task`` step.""" + +from __future__ import annotations + +from typing import Any + +from app.automations.registries.actions.types import ( + ActionContext, + ActionHandler, +) +from app.automations.schemas.actions import AgentTaskActionParams + +from .invoke import run_agent_task + + +def build_handler(ctx: ActionContext) -> ActionHandler: + """Return a handler closure that validates params and runs the agent task.""" + + async def handle(params: dict[str, Any]) -> dict[str, Any]: + validated = AgentTaskActionParams.model_validate(params) + return await run_agent_task( + ctx=ctx, + query=validated.query, + auto_approve_all=validated.auto_approve_all, + ) + + return handle diff --git a/surfsense_backend/app/automations/actions/agent_task/finalize.py b/surfsense_backend/app/automations/actions/agent_task/finalize.py new file mode 100644 index 000000000..d5f1f95f6 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/finalize.py @@ -0,0 +1,44 @@ +"""Extract the agent's final assistant text from the terminal invoke result.""" + +from __future__ import annotations + +from typing import Any + +from langchain_core.messages import AIMessage + + +def extract_final_assistant_message(result: Any) -> str | None: + """Return the last ``AIMessage`` text content, or ``None`` if there isn't one. + + Multi-part messages (content lists) are flattened by concatenating ``text`` + parts in order. Non-string content (tool calls, images) is skipped. + """ + if not isinstance(result, dict): + return None + messages = result.get("messages") + if not isinstance(messages, list): + return None + + for msg in reversed(messages): + if not isinstance(msg, AIMessage): + continue + return _content_to_text(msg.content) + return None + + +def _content_to_text(content: Any) -> str | None: + if isinstance(content, str): + text = content.strip() + return text or None + if isinstance(content, list): + parts: list[str] = [] + for part in content: + if isinstance(part, str): + parts.append(part) + elif isinstance(part, dict) and part.get("type") == "text": + text = part.get("text") + if isinstance(text, str): + parts.append(text) + joined = "".join(parts).strip() + return joined or None + return None diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py new file mode 100644 index 000000000..aa849d7e2 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -0,0 +1,97 @@ +"""Run one ``agent_task`` invocation: ainvoke + auto-decision resume loop.""" + +from __future__ import annotations + +import time +import uuid +from typing import Any + +from langchain_core.messages import HumanMessage +from langgraph.types import Command + +from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.automations.registries.actions.types import ActionContext +from app.db import ChatVisibility, async_session_maker + +from .auto_decide import build_auto_decisions +from .dependencies import build_dependencies +from .finalize import extract_final_assistant_message + +# Cap on HITL resume iterations. The agent should not need this many turns in one +# step; treat overshoot as a runaway and fail the step. +_MAX_RESUMES = 50 + + +async def run_agent_task( + *, + ctx: ActionContext, + query: str, + auto_approve_all: bool, +) -> dict[str, Any]: + """Invoke multi_agent_chat for one rendered query and return its outcome. + + Opens its own DB session so the executor's bookkeeping session isn't tied + up for the entire invocation. The LangGraph ``thread_id`` (a fresh UUID) + is returned as ``agent_session_id`` for later inspection. + """ + agent_session_id = str(uuid.uuid4()) + user_id = str(ctx.creator_user_id) if ctx.creator_user_id else None + decision = "approve" if auto_approve_all else "reject" + + async with async_session_maker() as agent_session: + deps = await build_dependencies( + session=agent_session, + search_space_id=ctx.search_space_id, + ) + + agent = await create_multi_agent_chat_deep_agent( + llm=deps.llm, + search_space_id=ctx.search_space_id, + db_session=agent_session, + connector_service=deps.connector_service, + checkpointer=deps.checkpointer, + user_id=user_id, + thread_id=None, + agent_config=deps.agent_config, + firecrawl_api_key=deps.firecrawl_api_key, + thread_visibility=ChatVisibility.PRIVATE, + ) + + request_id = f"automation:{ctx.run_id}:{ctx.step_id}" + turn_id = f"{request_id}:{int(time.time() * 1000)}" + input_state: dict[str, Any] = { + "messages": [HumanMessage(content=query)], + "search_space_id": ctx.search_space_id, + "request_id": request_id, + "turn_id": turn_id, + } + config: dict[str, Any] = { + "configurable": { + "thread_id": agent_session_id, + "request_id": request_id, + "turn_id": turn_id, + }, + "recursion_limit": 10_000, + } + + result = await agent.ainvoke(input_state, config=config) + + resumes = 0 + while True: + state = await agent.aget_state(config) + if not getattr(state, "interrupts", None): + break + if resumes >= _MAX_RESUMES: + raise RuntimeError( + f"agent_task exceeded {_MAX_RESUMES} HITL resume iterations" + ) + lg_resume_map, routed = build_auto_decisions(state, decision) + config["configurable"]["surfsense_resume_value"] = routed + result = await agent.ainvoke(Command(resume=lg_resume_map), config=config) + resumes += 1 + + return { + "agent_session_id": agent_session_id, + "final_message": extract_final_assistant_message(result), + "resumes": resumes, + } diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/registries/actions/agent_task.py index beba455cc..51ee0eb7f 100644 --- a/surfsense_backend/app/automations/registries/actions/agent_task.py +++ b/surfsense_backend/app/automations/registries/actions/agent_task.py @@ -2,31 +2,18 @@ from __future__ import annotations -from typing import Any - +from app.automations.actions.agent_task import build_handler from app.automations.schemas.actions import AgentTaskActionParams from .store import register_action -from .types import ActionContext, ActionDefinition, ActionHandler - - -def _build_handler(ctx: ActionContext) -> ActionHandler: - """Bind run/session context to the agent_task handler. Real wiring lands in Phase 4b.""" - del ctx # ignored by the stub; real handler will consume it - - async def handle(params: dict[str, Any]) -> dict[str, Any]: - AgentTaskActionParams.model_validate(params) - return {"status": "stubbed"} - - return handle - +from .types import ActionDefinition AGENT_TASK_ACTION = ActionDefinition( type="agent_task", name="Agent task", - description="Run an agent task with a scoped tool allowlist.", + description="Run a multi_agent_chat turn from an automation step.", params_schema=AgentTaskActionParams.model_json_schema(), - build_handler=_build_handler, + build_handler=build_handler, ) register_action(AGENT_TASK_ACTION) diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py index 348db8095..b0e99a78b 100644 --- a/surfsense_backend/app/automations/schemas/actions/agent_task.py +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -2,26 +2,20 @@ from __future__ import annotations -from typing import Any - from pydantic import BaseModel, ConfigDict, Field class AgentTaskActionParams(BaseModel): - """Run an agent task with a scoped tool allowlist.""" + """Run a multi_agent_chat turn from an automation step.""" model_config = ConfigDict(extra="forbid") - prompt: str = Field(..., min_length=1, description="Task prompt; rendered at execute time.") - tools: list[str] = Field( - default_factory=list, - description="Tool identifiers the agent may call. Empty = no tool access.", + query: str = Field( + ..., + min_length=1, + description="User query for the agent; rendered at execute time.", ) - model: str | None = Field( - default=None, - description="Model identifier. Defaults to the search space's agent_llm_id.", - ) - output_schema: dict[str, Any] | None = Field( - default=None, - description="JSON Schema (draft 2020-12) the agent must return. Recommended.", + auto_approve_all: bool = Field( + default=False, + description="If true, every HITL approval is auto-approved; otherwise rejected.", ) From 8c32455818094287faf3f3bfa6e602b98616d138 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:07:20 +0200 Subject: [PATCH 054/133] refactor(automations): vertical-slice actions and triggers by domain --- .../app/automations/actions/__init__.py | 25 +++++++++++++- .../actions/agent_task/__init__.py | 12 +++++-- .../agent_task/definition.py} | 11 +++---- .../automations/actions/agent_task/factory.py | 8 ++--- .../automations/actions/agent_task/invoke.py | 3 +- .../agent_task/params.py} | 0 .../{registries => }/actions/store.py | 0 .../{registries => }/actions/types.py | 0 .../app/automations/registries/__init__.py | 33 ------------------- .../registries/actions/__init__.py | 19 ----------- .../app/automations/runtime/executor.py | 2 +- .../app/automations/runtime/step.py | 4 +-- .../app/automations/schemas/__init__.py | 13 ++++---- .../automations/schemas/actions/__init__.py | 9 ----- .../automations/schemas/triggers/__init__.py | 11 ------- .../{registries => }/triggers/__init__.py | 6 +++- .../automations/triggers/manual/__init__.py | 10 ++++++ .../manual/definition.py} | 9 +++-- .../manual.py => triggers/manual/params.py} | 0 .../automations/triggers/schedule/__init__.py | 10 ++++++ .../schedule/definition.py} | 9 +++-- .../schedule/params.py} | 0 .../{registries => }/triggers/store.py | 0 .../{registries => }/triggers/types.py | 0 24 files changed, 86 insertions(+), 108 deletions(-) rename surfsense_backend/app/automations/{registries/actions/agent_task.py => actions/agent_task/definition.py} (54%) rename surfsense_backend/app/automations/{schemas/actions/agent_task.py => actions/agent_task/params.py} (100%) rename surfsense_backend/app/automations/{registries => }/actions/store.py (100%) rename surfsense_backend/app/automations/{registries => }/actions/types.py (100%) delete mode 100644 surfsense_backend/app/automations/registries/__init__.py delete mode 100644 surfsense_backend/app/automations/registries/actions/__init__.py delete mode 100644 surfsense_backend/app/automations/schemas/actions/__init__.py delete mode 100644 surfsense_backend/app/automations/schemas/triggers/__init__.py rename surfsense_backend/app/automations/{registries => }/triggers/__init__.py (61%) create mode 100644 surfsense_backend/app/automations/triggers/manual/__init__.py rename surfsense_backend/app/automations/{registries/triggers/manual.py => triggers/manual/definition.py} (58%) rename surfsense_backend/app/automations/{schemas/triggers/manual.py => triggers/manual/params.py} (100%) create mode 100644 surfsense_backend/app/automations/triggers/schedule/__init__.py rename surfsense_backend/app/automations/{registries/triggers/schedule.py => triggers/schedule/definition.py} (64%) rename surfsense_backend/app/automations/{schemas/triggers/schedule.py => triggers/schedule/params.py} (100%) rename surfsense_backend/app/automations/{registries => }/triggers/store.py (100%) rename surfsense_backend/app/automations/{registries => }/triggers/types.py (100%) diff --git a/surfsense_backend/app/automations/actions/__init__.py b/surfsense_backend/app/automations/actions/__init__.py index 2a518c1db..9ef091cb3 100644 --- a/surfsense_backend/app/automations/actions/__init__.py +++ b/surfsense_backend/app/automations/actions/__init__.py @@ -1 +1,24 @@ -"""Action implementations. One subpackage per built-in action type.""" +"""Actions domain: registry surface + built-in action packages. + +Each action lives in its own subpackage (``agent_task/``, ...) and self-registers +at import time via its ``definition`` module. Side-effect imports below ensure +the registry is populated whenever anyone touches the actions package. +""" + +from __future__ import annotations + +from .store import all_actions, get_action, register_action +from .types import ActionContext, ActionDefinition, ActionHandler, ActionHandlerFactory + +__all__ = [ + "ActionContext", + "ActionDefinition", + "ActionHandler", + "ActionHandlerFactory", + "all_actions", + "get_action", + "register_action", +] + +# Built-in actions self-register at import time. +from . import agent_task # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/actions/agent_task/__init__.py b/surfsense_backend/app/automations/actions/agent_task/__init__.py index ecf79b448..308812211 100644 --- a/surfsense_backend/app/automations/actions/agent_task/__init__.py +++ b/surfsense_backend/app/automations/actions/agent_task/__init__.py @@ -1,7 +1,15 @@ -"""``agent_task`` action: spin up multi_agent_chat for one rendered query.""" +"""``agent_task`` action: spin up multi_agent_chat for one rendered query. + +Imports ``definition`` for its side-effect (self-registration on the actions +registry) and re-exports ``build_handler`` for direct consumers. +""" from __future__ import annotations from .factory import build_handler +from .params import AgentTaskActionParams -__all__ = ["build_handler"] +__all__ = ["AgentTaskActionParams", "build_handler"] + +# Side-effect: register on the actions store. +from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/actions/agent_task/definition.py similarity index 54% rename from surfsense_backend/app/automations/registries/actions/agent_task.py rename to surfsense_backend/app/automations/actions/agent_task/definition.py index 51ee0eb7f..d7db5cfcd 100644 --- a/surfsense_backend/app/automations/registries/actions/agent_task.py +++ b/surfsense_backend/app/automations/actions/agent_task/definition.py @@ -1,12 +1,11 @@ -"""Built-in ``agent_task`` action. Self-registers at import time.""" +"""``agent_task`` ``ActionDefinition`` registration.""" from __future__ import annotations -from app.automations.actions.agent_task import build_handler -from app.automations.schemas.actions import AgentTaskActionParams - -from .store import register_action -from .types import ActionDefinition +from ..store import register_action +from ..types import ActionDefinition +from .factory import build_handler +from .params import AgentTaskActionParams AGENT_TASK_ACTION = ActionDefinition( type="agent_task", diff --git a/surfsense_backend/app/automations/actions/agent_task/factory.py b/surfsense_backend/app/automations/actions/agent_task/factory.py index a0d867f38..18a408e13 100644 --- a/surfsense_backend/app/automations/actions/agent_task/factory.py +++ b/surfsense_backend/app/automations/actions/agent_task/factory.py @@ -4,13 +4,9 @@ from __future__ import annotations from typing import Any -from app.automations.registries.actions.types import ( - ActionContext, - ActionHandler, -) -from app.automations.schemas.actions import AgentTaskActionParams - +from ..types import ActionContext, ActionHandler from .invoke import run_agent_task +from .params import AgentTaskActionParams def build_handler(ctx: ActionContext) -> ActionHandler: diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index aa849d7e2..a37e9beed 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -10,9 +10,10 @@ from langchain_core.messages import HumanMessage from langgraph.types import Command from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent -from app.automations.registries.actions.types import ActionContext from app.db import ChatVisibility, async_session_maker +from ..types import ActionContext + from .auto_decide import build_auto_decisions from .dependencies import build_dependencies from .finalize import extract_final_assistant_message diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/actions/agent_task/params.py similarity index 100% rename from surfsense_backend/app/automations/schemas/actions/agent_task.py rename to surfsense_backend/app/automations/actions/agent_task/params.py diff --git a/surfsense_backend/app/automations/registries/actions/store.py b/surfsense_backend/app/automations/actions/store.py similarity index 100% rename from surfsense_backend/app/automations/registries/actions/store.py rename to surfsense_backend/app/automations/actions/store.py diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/actions/types.py similarity index 100% rename from surfsense_backend/app/automations/registries/actions/types.py rename to surfsense_backend/app/automations/actions/types.py diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py deleted file mode 100644 index f6af3817b..000000000 --- a/surfsense_backend/app/automations/registries/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Action and trigger registries — populated at process startup.""" - -from __future__ import annotations - -from .actions import ( - ActionContext, - ActionDefinition, - ActionHandler, - ActionHandlerFactory, - all_actions, - get_action, - register_action, -) -from .triggers import ( - TriggerDefinition, - all_triggers, - get_trigger, - register_trigger, -) - -__all__ = [ - "ActionContext", - "ActionDefinition", - "ActionHandler", - "ActionHandlerFactory", - "TriggerDefinition", - "all_actions", - "all_triggers", - "get_action", - "get_trigger", - "register_action", - "register_trigger", -] diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py deleted file mode 100644 index b95c634f2..000000000 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Action registry.""" - -from __future__ import annotations - -from .store import all_actions, get_action, register_action -from .types import ActionContext, ActionDefinition, ActionHandler, ActionHandlerFactory - -__all__ = [ - "ActionContext", - "ActionDefinition", - "ActionHandler", - "ActionHandlerFactory", - "all_actions", - "get_action", - "register_action", -] - -# Built-in actions self-register at import time. -from . import agent_task # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index e9e55b02d..ced44fb9b 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun -from app.automations.registries.actions.types import ActionContext +from app.automations.actions.types import ActionContext from app.automations.schemas.definition.envelope import AutomationDefinition from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py index 76e3ba171..ac18b5e1f 100644 --- a/surfsense_backend/app/automations/runtime/step.py +++ b/surfsense_backend/app/automations/runtime/step.py @@ -6,8 +6,8 @@ from collections.abc import Mapping from datetime import UTC, datetime from typing import Any -from app.automations.registries import get_action -from app.automations.registries.actions.types import ActionContext +from app.automations.actions import get_action +from app.automations.actions.types import ActionContext from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import evaluate_predicate, render_value diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 8659ac9c9..2e2d60f12 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -1,8 +1,13 @@ -"""Schemas for the automation definition and per-type configs.""" +"""Schemas for the automation definition envelope. + +Per-action and per-trigger params schemas live with the action/trigger +implementations (``app.automations.actions..params`` / +``app.automations.triggers..params``); only the cross-cutting envelope +lives here. +""" from __future__ import annotations -from .actions import AgentTaskActionParams from .definition import ( AutomationDefinition, Execution, @@ -11,16 +16,12 @@ from .definition import ( PlanStep, TriggerSpec, ) -from .triggers import ManualTriggerParams, ScheduleTriggerParams __all__ = [ - "AgentTaskActionParams", "AutomationDefinition", "Execution", "Inputs", - "ManualTriggerParams", "Metadata", "PlanStep", - "ScheduleTriggerParams", "TriggerSpec", ] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py deleted file mode 100644 index c51d33b6a..000000000 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Per-action params schemas, one per action type.""" - -from __future__ import annotations - -from .agent_task import AgentTaskActionParams - -__all__ = [ - "AgentTaskActionParams", -] diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py deleted file mode 100644 index 3ddd26f95..000000000 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Per-trigger params schemas, one per trigger type.""" - -from __future__ import annotations - -from .manual import ManualTriggerParams -from .schedule import ScheduleTriggerParams - -__all__ = [ - "ManualTriggerParams", - "ScheduleTriggerParams", -] diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/triggers/__init__.py similarity index 61% rename from surfsense_backend/app/automations/registries/triggers/__init__.py rename to surfsense_backend/app/automations/triggers/__init__.py index e08dcce76..258b2fda9 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/triggers/__init__.py @@ -1,4 +1,8 @@ -"""Trigger registry.""" +"""Triggers domain: registry surface + built-in trigger packages. + +Each trigger lives in its own subpackage (``manual/``, ``schedule/``, ...) and +self-registers at import time via its ``definition`` module. +""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/triggers/manual/__init__.py b/surfsense_backend/app/automations/triggers/manual/__init__.py new file mode 100644 index 000000000..bd9b8bf43 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/manual/__init__.py @@ -0,0 +1,10 @@ +"""``manual`` trigger: fired by a user clicking ``Run now``.""" + +from __future__ import annotations + +from .params import ManualTriggerParams + +__all__ = ["ManualTriggerParams"] + +# Side-effect: register on the triggers store. +from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/triggers/manual.py b/surfsense_backend/app/automations/triggers/manual/definition.py similarity index 58% rename from surfsense_backend/app/automations/registries/triggers/manual.py rename to surfsense_backend/app/automations/triggers/manual/definition.py index 173c38655..9eb0282af 100644 --- a/surfsense_backend/app/automations/registries/triggers/manual.py +++ b/surfsense_backend/app/automations/triggers/manual/definition.py @@ -1,11 +1,10 @@ -"""Built-in ``manual`` trigger. Self-registers at import time.""" +"""``manual`` ``TriggerDefinition`` registration.""" from __future__ import annotations -from app.automations.schemas.triggers import ManualTriggerParams - -from .store import register_trigger -from .types import TriggerDefinition +from ..store import register_trigger +from ..types import TriggerDefinition +from .params import ManualTriggerParams MANUAL_TRIGGER = TriggerDefinition( type="manual", diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/triggers/manual/params.py similarity index 100% rename from surfsense_backend/app/automations/schemas/triggers/manual.py rename to surfsense_backend/app/automations/triggers/manual/params.py diff --git a/surfsense_backend/app/automations/triggers/schedule/__init__.py b/surfsense_backend/app/automations/triggers/schedule/__init__.py new file mode 100644 index 000000000..e24750850 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/schedule/__init__.py @@ -0,0 +1,10 @@ +"""``schedule`` trigger: fired on a cron schedule in a given timezone.""" + +from __future__ import annotations + +from .params import ScheduleTriggerParams + +__all__ = ["ScheduleTriggerParams"] + +# Side-effect: register on the triggers store. +from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/triggers/schedule.py b/surfsense_backend/app/automations/triggers/schedule/definition.py similarity index 64% rename from surfsense_backend/app/automations/registries/triggers/schedule.py rename to surfsense_backend/app/automations/triggers/schedule/definition.py index 0a6575f39..3f86d767c 100644 --- a/surfsense_backend/app/automations/registries/triggers/schedule.py +++ b/surfsense_backend/app/automations/triggers/schedule/definition.py @@ -1,11 +1,10 @@ -"""Built-in ``schedule`` trigger. Self-registers at import time.""" +"""``schedule`` ``TriggerDefinition`` registration.""" from __future__ import annotations -from app.automations.schemas.triggers import ScheduleTriggerParams - -from .store import register_trigger -from .types import TriggerDefinition +from ..store import register_trigger +from ..types import TriggerDefinition +from .params import ScheduleTriggerParams SCHEDULE_TRIGGER = TriggerDefinition( type="schedule", diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/triggers/schedule/params.py similarity index 100% rename from surfsense_backend/app/automations/schemas/triggers/schedule.py rename to surfsense_backend/app/automations/triggers/schedule/params.py diff --git a/surfsense_backend/app/automations/registries/triggers/store.py b/surfsense_backend/app/automations/triggers/store.py similarity index 100% rename from surfsense_backend/app/automations/registries/triggers/store.py rename to surfsense_backend/app/automations/triggers/store.py diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/triggers/types.py similarity index 100% rename from surfsense_backend/app/automations/registries/triggers/types.py rename to surfsense_backend/app/automations/triggers/types.py From 861b91004d4e5a0f992c8bbc0b2cb2be50e3ec73 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:20:23 +0200 Subject: [PATCH 055/133] refactor(automations): extract dispatch_run; move manual adapter under triggers/manual/dispatch.py --- .../app/automations/dispatch/__init__.py | 12 ++-- .../app/automations/dispatch/errors.py | 7 ++ .../app/automations/dispatch/run.py | 72 +++++++++++++++++++ .../automations/triggers/manual/__init__.py | 3 +- .../manual.py => triggers/manual/dispatch.py} | 56 +++------------ .../app/routes/automations_routes.py | 3 +- 6 files changed, 97 insertions(+), 56 deletions(-) create mode 100644 surfsense_backend/app/automations/dispatch/errors.py create mode 100644 surfsense_backend/app/automations/dispatch/run.py rename surfsense_backend/app/automations/{dispatch/manual.py => triggers/manual/dispatch.py} (51%) diff --git a/surfsense_backend/app/automations/dispatch/__init__.py b/surfsense_backend/app/automations/dispatch/__init__.py index 4a549a4ce..be8a36581 100644 --- a/surfsense_backend/app/automations/dispatch/__init__.py +++ b/surfsense_backend/app/automations/dispatch/__init__.py @@ -1,8 +1,8 @@ -"""Public dispatch surface for firing automations.""" +"""Generic dispatch primitives shared across trigger types.""" -from .manual import DispatchError, dispatch_manual_run +from __future__ import annotations -__all__ = [ - "DispatchError", - "dispatch_manual_run", -] +from .errors import DispatchError +from .run import dispatch_run + +__all__ = ["DispatchError", "dispatch_run"] diff --git a/surfsense_backend/app/automations/dispatch/errors.py b/surfsense_backend/app/automations/dispatch/errors.py new file mode 100644 index 000000000..75640a987 --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/errors.py @@ -0,0 +1,7 @@ +"""Dispatch errors raised when a fire request cannot be turned into a run.""" + +from __future__ import annotations + + +class DispatchError(Exception): + """A dispatch could not proceed (missing trigger, invalid inputs, ...).""" diff --git a/surfsense_backend/app/automations/dispatch/run.py b/surfsense_backend/app/automations/dispatch/run.py new file mode 100644 index 000000000..fd5107a18 --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/run.py @@ -0,0 +1,72 @@ +"""Generic run dispatch: validate, snapshot, persist, enqueue. Shared by every trigger.""" + +from __future__ import annotations + +from typing import Any + +import jsonschema +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.tasks.execute_run import automation_run_execute + +from .errors import DispatchError + + +async def dispatch_run( + *, + session: AsyncSession, + automation: Automation, + trigger: AutomationTrigger, + payload: dict[str, Any] | None, +) -> AutomationRun: + """Validate, snapshot the definition, persist an ``AutomationRun``, enqueue execution. + + Callers (trigger-specific adapters) are responsible for resolving + ``automation`` and ``trigger`` and for the trigger-side ``ACTIVE`` / + ``enabled`` guards. This function only handles what's identical across + every trigger type. + """ + try: + definition = AutomationDefinition.model_validate(automation.definition) + except Exception as exc: + raise DispatchError(f"invalid automation definition: {exc}") from exc + + resolved_inputs = _validate_inputs(definition, payload or {}) + snapshot = definition.model_dump(mode="json", by_alias=True) + + run = AutomationRun( + automation_id=automation.id, + trigger_id=trigger.id, + status=RunStatus.PENDING, + definition_snapshot=snapshot, + trigger_payload=payload, + resolved_inputs=resolved_inputs, + step_results=[], + artifacts=[], + ) + session.add(run) + await session.commit() + await session.refresh(run) + + automation_run_execute.apply_async( + args=[run.id], + time_limit=definition.execution.timeout_seconds, + ) + return run + + +def _validate_inputs( + definition: AutomationDefinition, payload: dict[str, Any] +) -> dict[str, Any]: + if definition.inputs is None or not definition.inputs.schema_: + return {} + try: + jsonschema.validate(instance=payload, schema=definition.inputs.schema_) + except jsonschema.ValidationError as exc: + raise DispatchError(f"inputs: {exc.message}") from exc + return payload diff --git a/surfsense_backend/app/automations/triggers/manual/__init__.py b/surfsense_backend/app/automations/triggers/manual/__init__.py index bd9b8bf43..65cca9270 100644 --- a/surfsense_backend/app/automations/triggers/manual/__init__.py +++ b/surfsense_backend/app/automations/triggers/manual/__init__.py @@ -2,9 +2,10 @@ from __future__ import annotations +from .dispatch import dispatch_manual_run from .params import ManualTriggerParams -__all__ = ["ManualTriggerParams"] +__all__ = ["ManualTriggerParams", "dispatch_manual_run"] # Side-effect: register on the triggers store. from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/dispatch/manual.py b/surfsense_backend/app/automations/triggers/manual/dispatch.py similarity index 51% rename from surfsense_backend/app/automations/dispatch/manual.py rename to surfsense_backend/app/automations/triggers/manual/dispatch.py index 221d6a3e2..750c99937 100644 --- a/surfsense_backend/app/automations/dispatch/manual.py +++ b/surfsense_backend/app/automations/triggers/manual/dispatch.py @@ -1,25 +1,18 @@ -"""Manual ``Run now`` dispatch: validate inputs, snapshot the definition, enqueue.""" +"""Manual ``Run now`` dispatch adapter: load + guard, then call generic dispatch.""" from __future__ import annotations from typing import Any -import jsonschema from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from app.automations.dispatch import DispatchError, dispatch_run from app.automations.persistence.enums.automation_status import AutomationStatus -from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.enums.trigger_type import TriggerType from app.automations.persistence.models.automation import Automation from app.automations.persistence.models.run import AutomationRun from app.automations.persistence.models.trigger import AutomationTrigger -from app.automations.schemas.definition.envelope import AutomationDefinition -from app.automations.tasks.execute_run import automation_run_execute - - -class DispatchError(Exception): - """A manual dispatch could not proceed (missing trigger, invalid inputs, ...).""" async def dispatch_manual_run( @@ -28,7 +21,7 @@ async def dispatch_manual_run( automation_id: int, payload: dict[str, Any] | None, ) -> AutomationRun: - """Validate, snapshot, persist, and enqueue an ``AutomationRun``.""" + """Find the automation + its enabled manual trigger, then run the generic dispatch.""" automation = await _load_automation(session, automation_id) if automation is None: raise DispatchError(f"automation {automation_id} not found") @@ -38,39 +31,18 @@ async def dispatch_manual_run( f"automation {automation_id} is {automation.status.value}, not active" ) - try: - definition = AutomationDefinition.model_validate(automation.definition) - except Exception as exc: - raise DispatchError(f"invalid automation definition: {exc}") from exc - trigger = await _find_manual_trigger(session, automation_id) if trigger is None: raise DispatchError( f"automation {automation_id} has no enabled manual trigger" ) - resolved_inputs = _validate_inputs(definition, payload or {}) - snapshot = definition.model_dump(mode="json", by_alias=True) - - run = AutomationRun( - automation_id=automation_id, - trigger_id=trigger.id, - status=RunStatus.PENDING, - definition_snapshot=snapshot, - trigger_payload=payload, - resolved_inputs=resolved_inputs, - step_results=[], - artifacts=[], + return await dispatch_run( + session=session, + automation=automation, + trigger=trigger, + payload=payload, ) - session.add(run) - await session.commit() - await session.refresh(run) - - automation_run_execute.apply_async( - args=[run.id], - time_limit=definition.execution.timeout_seconds, - ) - return run async def _load_automation( @@ -93,15 +65,3 @@ async def _find_manual_trigger( .limit(1) ) return (await session.execute(stmt)).scalar_one_or_none() - - -def _validate_inputs( - definition: AutomationDefinition, payload: dict[str, Any] -) -> dict[str, Any]: - if definition.inputs is None or not definition.inputs.schema_: - return {} - try: - jsonschema.validate(instance=payload, schema=definition.inputs.schema_) - except jsonschema.ValidationError as exc: - raise DispatchError(f"inputs: {exc.message}") from exc - return payload diff --git a/surfsense_backend/app/routes/automations_routes.py b/surfsense_backend/app/routes/automations_routes.py index 02c019625..6c169b199 100644 --- a/surfsense_backend/app/routes/automations_routes.py +++ b/surfsense_backend/app/routes/automations_routes.py @@ -8,8 +8,9 @@ from fastapi import APIRouter, Body, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.automations.dispatch import DispatchError, dispatch_manual_run +from app.automations.dispatch import DispatchError from app.automations.persistence.models.automation import Automation +from app.automations.triggers.manual import dispatch_manual_run from app.db import Permission, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission From f08b3164417f841644afe355edc794aa1a64f851 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:55:58 +0200 Subject: [PATCH 056/133] add next_fire_at to automation_triggers and croniter dep --- .../alembic/versions/144_add_automation_tables.py | 13 +++++++++++++ .../app/automations/persistence/models/trigger.py | 5 +++++ surfsense_backend/pyproject.toml | 1 + surfsense_backend/uv.lock | 14 ++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 8b59ee969..6daf4075f 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -89,6 +89,7 @@ def upgrade() -> None: params JSONB NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, last_fired_at TIMESTAMP WITH TIME ZONE, + next_fire_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); """ @@ -105,6 +106,17 @@ def upgrade() -> None: op.execute( "CREATE INDEX ix_automation_triggers_created_at ON automation_triggers(created_at);" ) + # Partial index for the schedule tick: only enabled schedule triggers + # with a scheduled next fire are ever scanned for due rows. + op.execute( + """ + CREATE INDEX ix_automation_triggers_due + ON automation_triggers (next_fire_at) + WHERE enabled = true + AND type = 'schedule' + AND next_fire_at IS NOT NULL; + """ + ) # automation_runs — the immutable per-fire execution record op.execute( @@ -148,6 +160,7 @@ def downgrade() -> None: op.execute("DROP INDEX IF EXISTS ix_automation_runs_automation_id;") op.execute("DROP TABLE IF EXISTS automation_runs;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_due;") op.execute("DROP INDEX IF EXISTS ix_automation_triggers_created_at;") op.execute("DROP INDEX IF EXISTS ix_automation_triggers_enabled;") op.execute("DROP INDEX IF EXISTS ix_automation_triggers_type;") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 7582234d4..b09bc3419 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -46,6 +46,11 @@ class AutomationTrigger(BaseModel, TimestampMixin): last_fired_at = Column(TIMESTAMP(timezone=True), nullable=True) + # Precomputed next fire moment in UTC; advanced after each fire by the + # schedule tick. NULL means the trigger has never been scheduled (the + # tick self-heals on first sight). Manual triggers leave this NULL. + next_fire_at = Column(TIMESTAMP(timezone=True), nullable=True) + automation = relationship("Automation", back_populates="triggers") runs = relationship( "AutomationRun", diff --git a/surfsense_backend/pyproject.toml b/surfsense_backend/pyproject.toml index 71c53caae..2ed0acca4 100644 --- a/surfsense_backend/pyproject.toml +++ b/surfsense_backend/pyproject.toml @@ -87,6 +87,7 @@ dependencies = [ "opentelemetry-instrumentation-httpx>=0.61b0", "opentelemetry-instrumentation-celery>=0.61b0", "opentelemetry-instrumentation-logging>=0.61b0", + "croniter>=2.0.0", ] [dependency-groups] diff --git a/surfsense_backend/uv.lock b/surfsense_backend/uv.lock index b902363dc..ba88153c5 100644 --- a/surfsense_backend/uv.lock +++ b/surfsense_backend/uv.lock @@ -1265,6 +1265,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848, upload-time = "2024-10-29T16:40:18.325Z" }, ] +[[package]] +name = "croniter" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, +] + [[package]] name = "cryptography" version = "46.0.6" @@ -8132,6 +8144,7 @@ dependencies = [ { name = "celery", extra = ["redis"] }, { name = "chonkie", extra = ["all"] }, { name = "composio" }, + { name = "croniter" }, { name = "datasets" }, { name = "daytona" }, { name = "deepagents" }, @@ -8228,6 +8241,7 @@ requires-dist = [ { name = "celery", extras = ["redis"], specifier = ">=5.5.3" }, { name = "chonkie", extras = ["all"], specifier = ">=1.5.0" }, { name = "composio", specifier = ">=0.10.9" }, + { name = "croniter", specifier = ">=2.0.0" }, { name = "datasets", specifier = ">=2.21.0" }, { name = "daytona", specifier = ">=0.146.0" }, { name = "deepagents", specifier = ">=0.4.12,<0.5" }, From 3b1d7c4389b0224f86dd38c7ea121e94d78f35ee Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:56:02 +0200 Subject: [PATCH 057/133] add cron-based schedule trigger --- .../automations/triggers/schedule/__init__.py | 10 +++- .../app/automations/triggers/schedule/cron.py | 37 ++++++++++++++ .../automations/triggers/schedule/dispatch.py | 48 +++++++++++++++++++ .../automations/triggers/schedule/params.py | 12 ++++- 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 surfsense_backend/app/automations/triggers/schedule/cron.py create mode 100644 surfsense_backend/app/automations/triggers/schedule/dispatch.py diff --git a/surfsense_backend/app/automations/triggers/schedule/__init__.py b/surfsense_backend/app/automations/triggers/schedule/__init__.py index e24750850..5587692b9 100644 --- a/surfsense_backend/app/automations/triggers/schedule/__init__.py +++ b/surfsense_backend/app/automations/triggers/schedule/__init__.py @@ -2,9 +2,17 @@ from __future__ import annotations +from .cron import InvalidCronError, compute_next_fire_at, validate_cron +from .dispatch import dispatch_schedule_run from .params import ScheduleTriggerParams -__all__ = ["ScheduleTriggerParams"] +__all__ = [ + "InvalidCronError", + "ScheduleTriggerParams", + "compute_next_fire_at", + "dispatch_schedule_run", + "validate_cron", +] # Side-effect: register on the triggers store. from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/triggers/schedule/cron.py b/surfsense_backend/app/automations/triggers/schedule/cron.py new file mode 100644 index 000000000..7155bab33 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/schedule/cron.py @@ -0,0 +1,37 @@ +"""Cron math for the ``schedule`` trigger: validate + advance ``next_fire_at``.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError + +from croniter import CroniterBadCronError, croniter + + +class InvalidCronError(ValueError): + """Raised when a cron expression or timezone fails validation.""" + + +def validate_cron(cron: str, timezone: str) -> None: + """Raise ``InvalidCronError`` if cron or timezone are unusable.""" + try: + ZoneInfo(timezone) + except ZoneInfoNotFoundError as exc: + raise InvalidCronError(f"unknown timezone {timezone!r}") from exc + + try: + croniter(cron) + except (CroniterBadCronError, ValueError) as exc: + raise InvalidCronError(f"invalid cron {cron!r}: {exc}") from exc + + +def compute_next_fire_at(cron: str, timezone: str, *, after: datetime) -> datetime: + """Return the next moment matching ``cron`` in ``timezone`` strictly after ``after``. + + The result is normalized to UTC for storage. ``after`` is converted into the + given timezone before evaluation so DST and IANA rules apply correctly. + """ + tz = ZoneInfo(timezone) + base = after.astimezone(tz) if after.tzinfo else after.replace(tzinfo=UTC).astimezone(tz) + nxt: datetime = croniter(cron, base).get_next(datetime) + return nxt.astimezone(UTC) diff --git a/surfsense_backend/app/automations/triggers/schedule/dispatch.py b/surfsense_backend/app/automations/triggers/schedule/dispatch.py new file mode 100644 index 000000000..fb4fcf686 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/schedule/dispatch.py @@ -0,0 +1,48 @@ +"""Schedule dispatch adapter: load + guard, then call generic dispatch.""" + +from __future__ import annotations + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError, dispatch_run +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.persistence.models.trigger import AutomationTrigger + + +async def dispatch_schedule_run( + *, + session: AsyncSession, + trigger: AutomationTrigger, +) -> AutomationRun: + """Fire one scheduled run for ``trigger``. + + The caller (the schedule tick) is responsible for selecting due triggers + and advancing ``next_fire_at`` / ``last_fired_at`` before invoking this. + """ + automation = await _load_automation(session, trigger.automation_id) + if automation is None: + raise DispatchError( + f"automation {trigger.automation_id} not found for trigger {trigger.id}" + ) + + if automation.status != AutomationStatus.ACTIVE: + raise DispatchError( + f"automation {trigger.automation_id} is {automation.status.value}, not active" + ) + + return await dispatch_run( + session=session, + automation=automation, + trigger=trigger, + payload=None, + ) + + +async def _load_automation( + session: AsyncSession, automation_id: int +) -> Automation | None: + stmt = select(Automation).where(Automation.id == automation_id) + return (await session.execute(stmt)).scalar_one_or_none() diff --git a/surfsense_backend/app/automations/triggers/schedule/params.py b/surfsense_backend/app/automations/triggers/schedule/params.py index 0418bd1d9..21da84f68 100644 --- a/surfsense_backend/app/automations/triggers/schedule/params.py +++ b/surfsense_backend/app/automations/triggers/schedule/params.py @@ -2,7 +2,9 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from .cron import InvalidCronError, validate_cron class ScheduleTriggerParams(BaseModel): @@ -10,3 +12,11 @@ class ScheduleTriggerParams(BaseModel): cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) timezone: str = Field(..., description="IANA timezone.", examples=["Africa/Kigali"]) + + @model_validator(mode="after") + def _validate(self) -> ScheduleTriggerParams: + try: + validate_cron(self.cron, self.timezone) + except InvalidCronError as exc: + raise ValueError(str(exc)) from exc + return self From d84240a630f93ba1bd815f54e743c8b6d1acdca1 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:56:07 +0200 Subject: [PATCH 058/133] add schedule tick task and beat entry --- .../app/automations/tasks/schedule_tick.py | 159 ++++++++++++++++++ surfsense_backend/app/celery_app.py | 11 ++ 2 files changed, 170 insertions(+) create mode 100644 surfsense_backend/app/automations/tasks/schedule_tick.py diff --git a/surfsense_backend/app/automations/tasks/schedule_tick.py b/surfsense_backend/app/automations/tasks/schedule_tick.py new file mode 100644 index 000000000..cade621c7 --- /dev/null +++ b/surfsense_backend/app/automations/tasks/schedule_tick.py @@ -0,0 +1,159 @@ +"""Celery Beat tick that fires due ``schedule`` triggers. + +Runs every minute. Each tick performs two passes: + +1. **Self-heal**: enabled schedule triggers with NULL ``next_fire_at`` get + it computed from their ``cron`` + ``timezone`` (e.g. fresh inserts or + rows restored from backup). +2. **Claim & fire**: due rows are locked with ``FOR UPDATE SKIP LOCKED``, + their ``next_fire_at`` is advanced and ``last_fired_at`` is set, and + ``dispatch_schedule_run`` is invoked for each. Dispatch errors are + logged; a missed fire stays missed (matches K8s CronJob / Airflow + ``catchup=False`` semantics). +""" + +from __future__ import annotations + +import logging +from datetime import UTC, datetime + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.triggers.schedule import ( + InvalidCronError, + compute_next_fire_at, + dispatch_schedule_run, +) +from app.celery_app import celery_app +from app.tasks.celery_tasks import get_celery_session_maker, run_async_celery_task + +logger = logging.getLogger(__name__) + +TASK_NAME = "automation_schedule_tick" + +# Cap rows touched per tick so a backlog of due triggers can't starve the +# worker; remaining rows fire on the next tick. +_TICK_BATCH = 200 + + +@celery_app.task(name=TASK_NAME) +def automation_schedule_tick() -> None: + """Tick once: self-heal NULL next_fire_at, claim due rows, fire each.""" + return run_async_celery_task(_tick) + + +async def _tick() -> None: + session_maker = get_celery_session_maker() + async with session_maker() as session: + now = datetime.now(UTC) + + await _self_heal_null_next_fire(session, now=now) + + claimed_ids = await _claim_due_triggers(session, now=now) + if not claimed_ids: + return + + for trigger_id in claimed_ids: + await _fire_one(session, trigger_id=trigger_id) + + +async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> None: + """Backfill ``next_fire_at`` for enabled schedule triggers missing it.""" + stmt = ( + select(AutomationTrigger) + .where( + AutomationTrigger.type == TriggerType.SCHEDULE, + AutomationTrigger.enabled.is_(True), + AutomationTrigger.next_fire_at.is_(None), + ) + .limit(_TICK_BATCH) + ) + triggers = (await session.execute(stmt)).scalars().all() + if not triggers: + return + + for trigger in triggers: + try: + trigger.next_fire_at = compute_next_fire_at( + trigger.params["cron"], + trigger.params["timezone"], + after=now, + ) + except (InvalidCronError, KeyError, TypeError) as exc: + logger.warning( + "automation_trigger %d has invalid schedule params, disabling: %s", + trigger.id, + exc, + ) + trigger.enabled = False + + await session.commit() + + +async def _claim_due_triggers( + session: AsyncSession, *, now: datetime +) -> list[int]: + """Lock and advance due rows; return claimed trigger ids.""" + stmt = ( + select(AutomationTrigger) + .where( + AutomationTrigger.type == TriggerType.SCHEDULE, + AutomationTrigger.enabled.is_(True), + AutomationTrigger.next_fire_at.isnot(None), + AutomationTrigger.next_fire_at <= now, + ) + .order_by(AutomationTrigger.next_fire_at) + .limit(_TICK_BATCH) + .with_for_update(skip_locked=True) + ) + triggers = (await session.execute(stmt)).scalars().all() + if not triggers: + return [] + + claimed: list[int] = [] + for trigger in triggers: + try: + trigger.next_fire_at = compute_next_fire_at( + trigger.params["cron"], + trigger.params["timezone"], + after=now, + ) + except (InvalidCronError, KeyError, TypeError) as exc: + logger.warning( + "automation_trigger %d has invalid schedule params, disabling: %s", + trigger.id, + exc, + ) + trigger.enabled = False + continue + + trigger.last_fired_at = now + claimed.append(trigger.id) + + await session.commit() + return claimed + + +async def _fire_one(session: AsyncSession, *, trigger_id: int) -> None: + """Reload the trigger post-commit and dispatch a run for it.""" + trigger = await session.get(AutomationTrigger, trigger_id) + if trigger is None: + return + + try: + run = await dispatch_schedule_run(session=session, trigger=trigger) + logger.info( + "scheduled fire: trigger=%d automation=%d run=%d", + trigger_id, + trigger.automation_id, + run.id, + ) + except Exception: + logger.exception( + "scheduled fire failed for trigger %d (next attempt at next match)", + trigger_id, + ) + await session.rollback() diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index 569178239..9169592fd 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -189,6 +189,7 @@ celery_app = Celery( "app.tasks.celery_tasks.stale_notification_cleanup_task", "app.tasks.celery_tasks.stripe_reconciliation_task", "app.automations.tasks.execute_run", + "app.automations.tasks.schedule_tick", ], ) @@ -283,4 +284,14 @@ celery_app.conf.beat_schedule = { "expires": 60, }, }, + # Fire due automation schedule triggers. Ticks every minute; per-row cron + # math is precomputed (next_fire_at column) so the tick is an indexed + # lookup, not N cron evaluations. + "automation-schedule-tick": { + "task": "automation_schedule_tick", + "schedule": crontab(minute="*"), + "options": { + "expires": 50, + }, + }, } From dd6bc30f98a5fa82848b86d8a621ad78f5042ba6 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 18:56:16 +0200 Subject: [PATCH 059/133] move automations api into vertical slice with service layer --- .../app/automations/api/__init__.py | 12 ++++ .../app/automations/api/automation.py | 22 +++++++ .../app/automations/services/__init__.py | 7 ++ .../app/automations/services/automation.py | 65 +++++++++++++++++++ surfsense_backend/app/routes/__init__.py | 2 +- .../app/routes/automations_routes.py | 56 ---------------- 6 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 surfsense_backend/app/automations/api/__init__.py create mode 100644 surfsense_backend/app/automations/api/automation.py create mode 100644 surfsense_backend/app/automations/services/__init__.py create mode 100644 surfsense_backend/app/automations/services/automation.py delete mode 100644 surfsense_backend/app/routes/automations_routes.py diff --git a/surfsense_backend/app/automations/api/__init__.py b/surfsense_backend/app/automations/api/__init__.py new file mode 100644 index 000000000..459c6c1b4 --- /dev/null +++ b/surfsense_backend/app/automations/api/__init__.py @@ -0,0 +1,12 @@ +"""HTTP layer for the automations feature.""" + +from __future__ import annotations + +from fastapi import APIRouter + +from .automation import router as automation_router + +router = APIRouter() +router.include_router(automation_router) + +__all__ = ["router"] diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py new file mode 100644 index 000000000..42163f74d --- /dev/null +++ b/surfsense_backend/app/automations/api/automation.py @@ -0,0 +1,22 @@ +"""Routes for the ``Automation`` resource.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, Depends + +from app.automations.services import AutomationService, get_automation_service + +router = APIRouter() + + +@router.post("/automations/{automation_id}/run") +async def run_automation_now( + automation_id: int, + payload: dict[str, Any] | None = Body(default=None), + service: AutomationService = Depends(get_automation_service), +) -> dict[str, Any]: + """Fire a manual run.""" + run = await service.run_now(automation_id=automation_id, payload=payload) + return {"run_id": run.id, "status": run.status.value} diff --git a/surfsense_backend/app/automations/services/__init__.py b/surfsense_backend/app/automations/services/__init__.py new file mode 100644 index 000000000..f0a97d216 --- /dev/null +++ b/surfsense_backend/app/automations/services/__init__.py @@ -0,0 +1,7 @@ +"""Service layer for the automations feature.""" + +from __future__ import annotations + +from .automation import AutomationService, get_automation_service + +__all__ = ["AutomationService", "get_automation_service"] diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py new file mode 100644 index 000000000..2a921e331 --- /dev/null +++ b/surfsense_backend/app/automations/services/automation.py @@ -0,0 +1,65 @@ +"""``AutomationService`` — orchestration for the ``Automation`` resource.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.triggers.manual import dispatch_manual_run +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + + +class AutomationService: + """Service for the ``Automation`` resource.""" + + def __init__(self, *, session: AsyncSession, user: User) -> None: + self.session = session + self.user = user + + async def run_now( + self, + *, + automation_id: int, + payload: dict[str, Any] | None, + ) -> AutomationRun: + """Fire a manual run for ``automation_id``.""" + automation = await self._get_automation_or_raise(automation_id) + await check_permission( + self.session, + self.user, + automation.search_space_id, + Permission.AUTOMATIONS_EXECUTE.value, + "You don't have permission to execute automations in this search space", + ) + + try: + return await dispatch_manual_run( + session=self.session, + automation_id=automation_id, + payload=payload, + ) + except DispatchError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + async def _get_automation_or_raise(self, automation_id: int) -> Automation: + """Get the automation by id; 404 if missing.""" + automation = await self.session.get(Automation, automation_id) + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + return automation + + +def get_automation_service( + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> AutomationService: + return AutomationService(session=session, user=user) diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 1d3ca2141..64c8c6585 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -7,7 +7,7 @@ from .agent_revert_route import router as agent_revert_router from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) -from .automations_routes import router as automations_router +from app.automations.api import router as automations_router from .chat_comments_routes import router as chat_comments_router from .circleback_webhook_route import router as circleback_webhook_router from .clickup_add_connector_route import router as clickup_add_connector_router diff --git a/surfsense_backend/app/routes/automations_routes.py b/surfsense_backend/app/routes/automations_routes.py deleted file mode 100644 index 6c169b199..000000000 --- a/surfsense_backend/app/routes/automations_routes.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Routes for automations. v1: manual ``Run now``.""" - -from __future__ import annotations - -from typing import Any - -from fastapi import APIRouter, Body, Depends, HTTPException -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.automations.dispatch import DispatchError -from app.automations.persistence.models.automation import Automation -from app.automations.triggers.manual import dispatch_manual_run -from app.db import Permission, User, get_async_session -from app.users import current_active_user -from app.utils.rbac import check_permission - -router = APIRouter() - - -@router.post("/automations/{automation_id}/run") -async def run_automation_now( - automation_id: int, - payload: dict[str, Any] | None = Body(default=None), - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -) -> dict[str, Any]: - """Fire an automation manually. Returns the new run id and status.""" - search_space_id = ( - await session.execute( - select(Automation.search_space_id).where(Automation.id == automation_id) - ) - ).scalar_one_or_none() - if search_space_id is None: - raise HTTPException( - status_code=404, detail=f"automation {automation_id} not found" - ) - - await check_permission( - session, - user, - search_space_id, - Permission.AUTOMATIONS_EXECUTE.value, - "You don't have permission to execute automations in this search space", - ) - - try: - run = await dispatch_manual_run( - session=session, - automation_id=automation_id, - payload=payload, - ) - except DispatchError as exc: - raise HTTPException(status_code=422, detail=str(exc)) from exc - - return {"run_id": run.id, "status": run.status.value} From 84d99f19a253ecf34c5e53285ce6cb65cd2d98c7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 19:10:20 +0200 Subject: [PATCH 060/133] automations(api): API request/response schemas --- .../app/automations/api/automation.py | 7 +- .../app/automations/api/schemas/__init__.py | 28 ++++++++ .../app/automations/api/schemas/automation.py | 64 +++++++++++++++++++ .../app/automations/api/schemas/run.py | 50 +++++++++++++++ .../app/automations/api/schemas/trigger.py | 43 +++++++++++++ 5 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 surfsense_backend/app/automations/api/schemas/__init__.py create mode 100644 surfsense_backend/app/automations/api/schemas/automation.py create mode 100644 surfsense_backend/app/automations/api/schemas/run.py create mode 100644 surfsense_backend/app/automations/api/schemas/trigger.py diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py index 42163f74d..4d0ce7209 100644 --- a/surfsense_backend/app/automations/api/automation.py +++ b/surfsense_backend/app/automations/api/automation.py @@ -6,17 +6,18 @@ from typing import Any from fastapi import APIRouter, Body, Depends +from app.automations.api.schemas import RunDispatched from app.automations.services import AutomationService, get_automation_service router = APIRouter() -@router.post("/automations/{automation_id}/run") +@router.post("/automations/{automation_id}/run", response_model=RunDispatched) async def run_automation_now( automation_id: int, payload: dict[str, Any] | None = Body(default=None), service: AutomationService = Depends(get_automation_service), -) -> dict[str, Any]: +) -> RunDispatched: """Fire a manual run.""" run = await service.run_now(automation_id=automation_id, payload=payload) - return {"run_id": run.id, "status": run.status.value} + return RunDispatched(run_id=run.id, status=run.status) diff --git a/surfsense_backend/app/automations/api/schemas/__init__.py b/surfsense_backend/app/automations/api/schemas/__init__.py new file mode 100644 index 000000000..a8a010a2c --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/__init__.py @@ -0,0 +1,28 @@ +"""Request/response schemas for the automations HTTP layer.""" + +from __future__ import annotations + +from .automation import ( + AutomationCreate, + AutomationDetail, + AutomationList, + AutomationSummary, + AutomationUpdate, +) +from .run import RunDetail, RunDispatched, RunList, RunSummary +from .trigger import TriggerCreate, TriggerDetail, TriggerUpdate + +__all__ = [ + "AutomationCreate", + "AutomationDetail", + "AutomationList", + "AutomationSummary", + "AutomationUpdate", + "RunDetail", + "RunDispatched", + "RunList", + "RunSummary", + "TriggerCreate", + "TriggerDetail", + "TriggerUpdate", +] diff --git a/surfsense_backend/app/automations/api/schemas/automation.py b/surfsense_backend/app/automations/api/schemas/automation.py new file mode 100644 index 000000000..c1defd417 --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/automation.py @@ -0,0 +1,64 @@ +"""Request/response schemas for the ``Automation`` resource.""" + +from __future__ import annotations + +from datetime import datetime + +from pydantic import BaseModel, ConfigDict, Field + +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.schemas.definition import AutomationDefinition + +from .trigger import TriggerCreate, TriggerDetail + + +class AutomationCreate(BaseModel): + """Create an automation, optionally with initial triggers (atomic).""" + + model_config = ConfigDict(extra="forbid") + + search_space_id: int + name: str = Field(..., min_length=1, max_length=200) + description: str | None = None + definition: AutomationDefinition + triggers: list[TriggerCreate] = Field(default_factory=list) + + +class AutomationUpdate(BaseModel): + """Partial update of an automation. Triggers are managed separately.""" + + model_config = ConfigDict(extra="forbid") + + name: str | None = Field(default=None, min_length=1, max_length=200) + description: str | None = None + status: AutomationStatus | None = None + definition: AutomationDefinition | None = None + + +class AutomationSummary(BaseModel): + """Lightweight automation view for list endpoints.""" + + model_config = ConfigDict(from_attributes=True) + + id: int + search_space_id: int + name: str + description: str | None = None + status: AutomationStatus + version: int + created_at: datetime + updated_at: datetime + + +class AutomationDetail(AutomationSummary): + """Full automation view including definition and attached triggers.""" + + definition: AutomationDefinition + triggers: list[TriggerDetail] = Field(default_factory=list) + + +class AutomationList(BaseModel): + """Paginated list of automations.""" + + items: list[AutomationSummary] + total: int diff --git a/surfsense_backend/app/automations/api/schemas/run.py b/surfsense_backend/app/automations/api/schemas/run.py new file mode 100644 index 000000000..789b6f674 --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/run.py @@ -0,0 +1,50 @@ +"""Response schemas for run sub-resources and run dispatch.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from pydantic import BaseModel, ConfigDict + +from app.automations.persistence.enums.run_status import RunStatus + + +class RunSummary(BaseModel): + """Lightweight run view for list endpoints.""" + + model_config = ConfigDict(from_attributes=True) + + id: int + automation_id: int + trigger_id: int | None = None + status: RunStatus + started_at: datetime | None = None + finished_at: datetime | None = None + created_at: datetime + + +class RunDetail(RunSummary): + """Full run view including snapshot, results and artifacts.""" + + definition_snapshot: dict[str, Any] + trigger_payload: dict[str, Any] | None = None + resolved_inputs: dict[str, Any] + step_results: list[dict[str, Any]] + output: dict[str, Any] | None = None + artifacts: list[dict[str, Any]] + error: dict[str, Any] | None = None + + +class RunList(BaseModel): + """Paginated list of runs.""" + + items: list[RunSummary] + total: int + + +class RunDispatched(BaseModel): + """Response of a successful run dispatch.""" + + run_id: int + status: RunStatus diff --git a/surfsense_backend/app/automations/api/schemas/trigger.py b/surfsense_backend/app/automations/api/schemas/trigger.py new file mode 100644 index 000000000..32afe7c60 --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/trigger.py @@ -0,0 +1,43 @@ +"""Request/response schemas for trigger sub-resources.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + +from app.automations.persistence.enums.trigger_type import TriggerType + + +class TriggerCreate(BaseModel): + """Attach a trigger to an automation.""" + + model_config = ConfigDict(extra="forbid") + + type: TriggerType + params: dict[str, Any] = Field(default_factory=dict) + enabled: bool = True + + +class TriggerUpdate(BaseModel): + """Partial update of an existing trigger.""" + + model_config = ConfigDict(extra="forbid") + + enabled: bool | None = None + params: dict[str, Any] | None = None + + +class TriggerDetail(BaseModel): + """Trigger as returned to clients.""" + + model_config = ConfigDict(from_attributes=True) + + id: int + type: TriggerType + params: dict[str, Any] + enabled: bool + last_fired_at: datetime | None = None + next_fire_at: datetime | None = None + created_at: datetime From 27ab367a13492426f315fc7e40c8d987d6f28b74 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 21:21:43 +0200 Subject: [PATCH 061/133] feat(automations): static_inputs on triggers + vertical-slice api/services --- automation-design-plan.md | 489 ++++++++---------- .../versions/144_add_automation_tables.py | 4 +- .../actions/agent_task/definition.py | 2 +- .../app/automations/actions/types.py | 8 +- .../app/automations/api/__init__.py | 4 + .../app/automations/api/automation.py | 83 ++- surfsense_backend/app/automations/api/run.py | 71 +++ .../app/automations/api/trigger.py | 55 ++ .../app/automations/dispatch/run.py | 18 +- .../app/automations/persistence/models/run.py | 5 +- .../automations/persistence/models/trigger.py | 4 + .../app/automations/runtime/executor.py | 2 +- .../{api/schemas => schemas/api}/__init__.py | 0 .../schemas => schemas/api}/automation.py | 0 .../{api/schemas => schemas/api}/run.py | 3 +- .../{api/schemas => schemas/api}/trigger.py | 3 + .../app/automations/services/__init__.py | 13 +- .../app/automations/services/automation.py | 159 +++++- .../app/automations/services/run.py | 93 ++++ .../app/automations/services/trigger.py | 143 +++++ .../app/automations/tasks/schedule_tick.py | 56 +- .../app/automations/templating/context.py | 4 +- .../automations/triggers/manual/definition.py | 3 +- .../automations/triggers/manual/dispatch.py | 11 +- .../triggers/schedule/definition.py | 7 +- .../automations/triggers/schedule/dispatch.py | 21 +- .../app/automations/triggers/types.py | 10 +- 27 files changed, 915 insertions(+), 356 deletions(-) create mode 100644 surfsense_backend/app/automations/api/run.py create mode 100644 surfsense_backend/app/automations/api/trigger.py rename surfsense_backend/app/automations/{api/schemas => schemas/api}/__init__.py (100%) rename surfsense_backend/app/automations/{api/schemas => schemas/api}/automation.py (100%) rename surfsense_backend/app/automations/{api/schemas => schemas/api}/run.py (92%) rename surfsense_backend/app/automations/{api/schemas => schemas/api}/trigger.py (87%) create mode 100644 surfsense_backend/app/automations/services/run.py create mode 100644 surfsense_backend/app/automations/services/trigger.py diff --git a/automation-design-plan.md b/automation-design-plan.md index f57385e31..db5f7a23c 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -34,24 +34,27 @@ system will survive feature growth: --- -## 2. The four-layer contract +## 2. The three-layer contract -The system is structured as four layers. Layers 1, 2, and 4 are defined by -SurfSense developers (at registration time). Layer 3 is what users write -(or the NL generator produces). The runtime reads all four to do its job. +The system is structured as three layers. Layers 1 and 3 are defined by +SurfSense developers (at registration time). Layer 2 is what users write +(or the NL generator produces). The runtime reads all three to do its job. | Layer | What it is | Defined by | | ----- | ---------- | ---------- | -| **1. Capability registry** | What this SurfSense instance can do | Developers, at startup | -| **2. Action contract** | Per-action input/output schema | Developers, at startup | -| **3. Automation definition** | One concrete saved automation | Users (or NL generator) | -| **4. Trigger contract** | Per-trigger config and payload schemas | Developers, at startup | +| **1. Action contract** | Per-action params and output schema | Developers, at startup | +| **2. Automation definition** | One concrete saved automation | Users (or NL generator) | +| **3. Trigger contract** | Per-trigger params and payload schemas | Developers, at startup | -Each layer constrains the one above. The runtime reads all four but doesn't -know what's in them ahead of time. That's how a new capability or trigger +Each layer constrains the next. The runtime reads all three but doesn't +know what's in them ahead of time. That's how a new action or trigger type becomes available across the engine without code changes outside its registration. +A unification layer below Layer 1 — one catalog of "things this SurfSense +instance can do," shared by automations, agents, and future surfaces — was +considered and deferred (§3). v1 actions are stand-alone. + ### Schema language Every shape in every layer is described in **JSON Schema (draft 2020-12).** @@ -66,167 +69,126 @@ extensions on top: --- -## 3. Capability registry (Layer 1) +## 3. Capability unification layer — deferred to post-v1 -A `Capability` is one discrete thing the SurfSense backend exposes — -"post a Slack message," "query the Search Space," "generate a podcast." It -is the atomic unit of "things automations can do." +Earlier drafts introduced a `Capability` registry as Layer 1: one catalog +of "things this SurfSense instance can do," shared by the automation +engine (as actions), the agent (as tools), and any future HTTP surface. +The motivation is real — one source of truth beats N parallel registries — +but v1 has a single action (`agent_task`) and a single consumer (the +automation engine). The five-field shape sketched earlier (`id`, +`description`, `input_schema`, `output_schema`, `handler`) cannot safely +host any non-trivial capability: it carries no caller identity, no +search-space scoping, and no authorization gate on tool delegation. +Building the abstraction with one consumer would lock in a shape that +doesn't survive the second consumer. -```python -@dataclass -class Capability: - id: str # "slack.post_message" - description: str # for the NL generator + UI label - input_schema: dict # JSON Schema - output_schema: dict # JSON Schema - handler: AsyncHandler -``` +The unification layer returns when the second consumer lands (Phase 2 +tight actions or Phase 4 MCP), redesigned from the start with: -### v1-minimum: five fields, nothing else +- A `CallContext` carrying caller user id, search space id, and run id, + passed to every handler invocation. +- Explicit scope declarations per capability (e.g. `reads:documents`, + `writes:slack`, `destructive`) for the authorization layer to read. +- A per-user, per-search-space filter consulted at both definition save + time (validating `agent_task.tools`) and run time (scoping the agent's + tool list to what the automation creator can delegate). -The Capability is **deliberately five fields in v1**. Every additional field -that earlier drafts considered (`name`, `required_credentials`, -`side_effects`, `expected_duration_seconds`, `cost_estimate`) has been -removed until a concrete consumer feature demands it. Authoring stays cheap -and the registry stays trivial to introspect: +Until then: -- `name` → folded into `description`. The UI can render a short label from - the first line of `description` or fall back to `id`. No separate field - needed in v1. -- `required_credentials` → returns when external-credential capabilities - ship (Phase 2). v1 capabilities run server-side with app config; nothing - to declare. -- `side_effects` → returns when RBAC inside automations or - `READ_ONLY`-only agent tool gating arrives. v1 capabilities are - hand-picked and all trusted code. -- `expected_duration_seconds` → returns when multi-queue routing ships. - Single Celery queue in v1. -- `cost_estimate` → never returns as a declared field; cost is measured - per run from a ledger, aggregated per Capability, and surfaced as a - historical average. Pre-flight checks are deferred. - -The runtime invariant: a Capability is **a typed, named, callable thing -the system can do.** Every consumer (executor, agent tool layer, future -HTTP API) sees the same five-field shape and uses it the same way. - -### Where capabilities live (v1) - -In v1, the capability registry is a single in-memory dict, populated at -process startup from native registrations in -`automations/registries/capabilities/`. Identical across all workers. -No database persistence, no closures rebuilt per worker. - -### MCP integration — deferred to Phase 4 - -The earlier two-tier registry (native + MCP-derived), the -`mcp_connections` / `mcp_tools` tables, the harvester, and the lazy -per-worker closure cache are **deferred to Phase 4** along with the -rest of the integration-tooling surface. They are removed from v1 -because: - -- v1 has no external connector capabilities (no Slack, Notion, Drive, - etc.). The only capabilities that will ship are server-side helpers - (search-space query / fetch) plus the loose `agent_task` action. -- Without external connectors, the lifecycle mismatch that motivates - the two-tier design (connect Monday, run Friday, workers restarted - in between) doesn't arise. A startup-time dict is sufficient. -- Phase 4 reintroduces this design as-is — the registry interface in - v1 is the same callable surface a Phase-4 MCP harvester will register - into. The deferral is additive, not a different design. - -See archived design at `docs/automation/archived/mcp-registry.md` once -v1 ships; for now the only consumer of the registry is the in-memory -native path. +- v1 actions are stand-alone units (Layer 1 below); the automation engine + reads its own action registry, nothing else. +- `agent_task.params.tools` is a forward-looking allowlist field with no + v1 semantics beyond "list of string identifiers." The handler's tool + resolution is opaque to the automation contract. ### Credentials — deferred to Phase 2 -The earlier per-call credential resolution pattern (`ctx.resolve_mcp_client`, -`ctx.resolve_http_client`, `ctx.resolve_llm`) is **deferred to Phase 2**. -v1 capabilities run server-side using app-level configuration; none of -the seven v1 capabilities needs per-user or per-connection auth. +External-credential handlers (Slack, email, etc.) require per-user or +per-connection auth. v1 actions run server-side with app-level +configuration. When tight actions ship in Phase 2, the credential design +lands as part of the unification redesign: connection IDs in the +definition (never tokens); credentials loaded per-call by the handler +context (never pre-loaded into worker memory); credentials never enter +LLM context. -When Phase 2 ships external-credential capabilities (Slack, email, etc.), -the three guarantees the original design promised are reintroduced -unchanged: +### MCP — deferred to Phase 4 -- Credentials never appear in the automation definition (connection IDs - only). -- Credentials never appear in the LLM's context (the host holds them - and uses them on the LLM's behalf when executing tool calls). -- Credentials are loaded per-call, not pre-loaded into worker memory. - -The Phase-2 design returns as-is; only the v1 surface is simplified. +External tool servers feeding tools into a shared registry land with the +rest of the integration tooling in Phase 4, after the unification layer +is in place. The two-tier registry, `mcp_connections` and `mcp_tools` +tables, and the harvester arrive as a single coherent step then. --- -## 4. Action contract (Layer 2) +## 4. Action contract -An `Action` is what a user references in a plan step. Most actions are -thin wrappers around one capability (e.g., `slack_post` wraps -`slack.post_message`). Some compose: `agent_task` is one action whose -handler invokes the LangGraph runtime, which in turn can call many -capabilities. +An `Action` is what a user references in a plan step. Some actions are +deterministic single-purpose handlers (`slack_post`, `send_email`); one +action (`agent_task`) hosts an LLM and a tool allowlist for cases where +judgment is needed. The contract is the same in both cases — only the +handler differs. ```python -@dataclass +@dataclass(frozen=True, slots=True) class ActionDefinition: - type: str # "agent_task", "slack_post" - name: str # for the UI - description: str # for the NL generator - config_schema: dict # JSON Schema for action.config - output_contract: dict | DynamicOutput # what it produces - uses_capabilities: list[str] # IDs from the registry - produces_artifacts: list[ArtifactSpec] # see §8 - handler: AsyncHandler + type: str # "agent_task", "slack_post" + name: str # short UI label + description: str # for the NL generator and the UI + params_schema: dict # JSON Schema for step.params + handler: ActionHandler ``` +This is the v1 shape: five fields, no handler context, no output +contract, no artifact declaration. The deferrals are intentional: + +- **`output_contract`** — Phase 2. Deterministic handlers will return + a fixed shape; v1's only action (`agent_task`) takes an + `output_schema` inside `params` and validates against that instead. +- **`produces_artifacts`** — Phase 5. Artifact lifecycle (storage, + signed URLs, retention) is its own design step; v1 handlers + persist their own outputs. +- **Handler context** — paired with the unification redesign (§3). + v1 handlers receive `(args)` only; per-user / per-search-space + behavior is not yet a v1 concern. + ### Tight vs loose actions Two patterns coexist by design: -- **Tight actions** (`slack_post`, `linear_create_issue`, `send_email`): - config_schema is fully specified, output_contract is fixed, handler is a - thin wrapper. ~20 LOC each. Used when the user knows exactly what they - want done — no LLM tokens spent on trivial work. +- **Tight actions** (`slack_post`, `linear_create_issue`, + `send_email`) — deterministic single-purpose handlers. ~20 LOC + each. **Phase 2.** +- **Loose actions** (`agent_task`) — params_schema accepts a `prompt`, + a `tools` allowlist, and an optional `output_schema` declaring what + the agent must return; the handler validates the agent's output + against it. **v1.** -- **Loose actions** (`agent_task`): config_schema accepts a `prompt` and a - `tools` allowlist; output_contract is *dynamic* — the user declares the - output shape they want via `output_schema` in the step config; the - handler asks the LLM to return that shape and validates. Used when - judgment is needed. - -The agent's tool list is **the same capabilities** that tight actions call -directly. One registry, two invocation modes. Adding a new MCP server gives -both modes access to its tools automatically. +The agent's `tools` allowlist resolves opaquely in v1; the redesigned +unification layer (§3) will give both invocation modes access to the +same vocabulary, with per-user authorization gating both. ### How names in the definition become function calls -The definition contains strings like `"action": "slack_post"`. The string is -just a name — it does not point to a function. At runtime, the executor -performs a **name-based lookup** against the action registry: +The definition contains strings like `"action": "agent_task"`. The +string is just a name — it does not point to a function. At runtime, +the executor performs a **name-based lookup** against the action +registry: ```python -# step.action is a string from the JSON definition, e.g. "slack_post" -action_def = _ACTION_REGISTRY[step.action] # dict lookup -handler = action_def.handler # Python callable -result = await handler(ctx, resolved_config) # invocation +action_def = action_registry.get(step.action) # dict lookup +handler = action_def.handler # Python callable +result = await handler(resolved_params) # invocation ``` -The registry is a Python dict (or a thin wrapper around one) populated at -process startup. Each entry in `automations/actions/*.py` calls a -`register_action(...)` function at module import time, putting its -`ActionDefinition` (including the handler function reference) into the -registry. +The registry is a Python dict populated at process startup. Each entry +in `automations/registries/actions/*.py` calls `register_action(...)` +at module import time, putting its `ActionDefinition` (including the +handler function reference) into the registry. -The same pattern applies to capabilities. The definition references -capabilities by ID (`"slack.post_message"`); the capability registry maps -the ID to a `Capability` object holding the handler. Definitions never -reference Python code directly — they reference names that the registry -resolves to code. - -This separation is what makes the contract portable. The definition is -pure data. The registry is the engine's runtime vocabulary. They meet at -name-based lookup; nothing else crosses the boundary. +The definition is pure data. The registry is the engine's runtime +vocabulary. They meet at name-based lookup; nothing else crosses the +boundary. ### The full expressive spectrum @@ -238,7 +200,7 @@ fully agentic. Six practical shapes worth recognizing: | **1. Direct call** | `slack_post` with literal channel and template | No LLM. ~200ms. Fractions of a cent. | | **2. Direct call with computed inputs** | `linear_create_issue` using `{{summary.title}}` from a prior step | No LLM for this step. Cheap. | | **3. Single-domain agent task** | `agent_task` with `tools: ["slack.*"]` only | One LLM, bounded toolset. | -| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named capabilities. | +| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named tools. | | **5. Multi-domain agent task, broad** | `agent_task` with `tools: ["slack.*", "github.*", "linear.*"]` | One LLM, large toolset, most agentic. | | **6. Composed plan** | `agent_task` (narrow) for thinking → `slack_post` + `linear_create_issue` for acting | Best cost-to-power ratio. | @@ -258,7 +220,7 @@ user's. --- -## 5. Automation definition (Layer 3) +## 5. Automation definition This is the JSON the user writes (or the NL generator produces). Stored in `automations.definition` as JSONB. @@ -287,7 +249,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in "triggers": [ { "type": "schedule", - "config": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } + "params": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } } ], @@ -295,7 +257,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in { "step_id": "research", "action": "agent_task", - "config": { + "params": { "prompt": "Find documents tagged {{inputs.tags}} indexed since {{inputs.since}}. Return JSON with bullets and source_doc_ids.", "tools": ["search_space.query", "search_space.fetch_document"], "model": "anthropic/claude-sonnet-4-7", @@ -313,7 +275,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in { "step_id": "deliver", "action": "slack_post", - "config": { + "params": { "channel_id": "C0123", "message_template": "*Competitor digest*\n\n{% for b in summary.bullets %}• {{b}}\n{% endfor %}" } @@ -325,11 +287,10 @@ This is the JSON the user writes (or the NL generator produces). Stored in "max_retries": 2, "retry_backoff": "exponential", "concurrency": "drop_if_running", - "budget_cap_usd": 1.50, "on_failure": [ /* steps to run if main plan fails after retries */ ] }, - "metadata": { "tags": ["digest"], "created_from_nl": true } + "metadata": { "tags": ["digest"] } } ``` @@ -340,7 +301,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in "step_id": "...", // unique within plan "action": "...", // references an ActionDefinition.type "when": "{{ ... }}", // optional Jinja expr → bool; false = skip - "config": { ... }, // validated against action's config_schema + "params": { ... }, // validated against action's params_schema "output_as": "...", // binds output to this name for later steps "max_retries": 0, // optional, overrides automation default "timeout_seconds": 1200 // optional, overrides automation default @@ -354,7 +315,7 @@ about it, or they compose automations through events (§7.5). --- -## 6. Trigger contract (Layer 4) +## 6. Trigger contract Three trigger types. That's the entire taxonomy. @@ -363,23 +324,12 @@ Three trigger types. That's the entire taxonomy. ```python TriggerDefinition( type="schedule", - config_schema={ - "type": "object", - "required": ["cron", "timezone"], - "properties": { - "cron": { "type": "string" }, - "timezone": { "type": "string", "format": "iana-timezone" } - } - }, - payload_schema={ - "type": "object", - "properties": { - "fired_at": { "type": "string", "format": "date-time" }, - "scheduled_for": { "type": "string", "format": "date-time" }, - "last_fired_at": { "type": "string", "format": "date-time" } - } - } + params_model=ScheduleTriggerParams, # cron + timezone ) +# At fire time the schedule producer emits runtime inputs +# (fired_at, scheduled_for, last_fired_at) which are merged with the +# trigger row's static_inputs (static wins) and validated against +# automation.definition.inputs.schema_. ``` Implementation: extends `app/utils/periodic_scheduler.py`, which already @@ -395,7 +345,7 @@ want an event trigger instead. ```python TriggerDefinition( type="webhook", - config_schema={ + params_schema={ "type": "object", "properties": { "input_mapping": { @@ -422,7 +372,7 @@ Dedups against runs in the last 24 hours. ```python TriggerDefinition( type="event", - config_schema={ + params_schema={ "type": "object", "required": ["event_type"], "properties": { @@ -485,11 +435,13 @@ Common path (after a trigger has fired): 4. **Snapshot the resolved definition** into the run row (immutable history) 5. Enqueue executor task on the single `automations_default` Celery queue -The cost-estimate pre-check (originally step 3) is **deferred**. -v1 capabilities do not declare `cost_estimate`; pre-flight budgeting -returns when a historical-cost ledger exists. The mid-flight budget -cap (§7.2) still kills the run if accumulated cost crosses -`budget_cap_usd`. +The cost-estimate pre-check (originally step 3) is **deferred**. v1 +actions do not declare cost estimates, the run row has no `cost_usd` +column, and no handler reports tokens used — so neither pre-flight +prediction nor mid-flight accumulation can be enforced. `Execution` +therefore does not expose `budget_cap_usd` in v1; it returns as a single +field addition the day the cost ledger ships (per-action cost reporting ++ `automation_runs.cost_usd` column + executor accumulation). Queue routing by `expected_duration_seconds` is **deferred** until load patterns justify a second queue. v1 uses a single queue. @@ -510,15 +462,15 @@ async def execute_run(run_id: int) -> None: if step.when and not evaluate_predicate(step.when, context | step_outputs): record_step_skipped(run, step); continue - resolved_config = render_config(step.config, context | step_outputs) + resolved_params = render_params(step.params, context | step_outputs) action = action_registry.get(step.action) - validate(resolved_config, action.config_schema) + validate(resolved_params, action.params_schema) try: result = await with_retries( action.handler, ctx=build_action_context(run, action), - args=resolved_config, + args=resolved_params, policy=step.retry_policy or run.execution.retry_policy, ) validate(result, step.output_schema) @@ -541,14 +493,20 @@ validated dict come back; it doesn't know that step was "smart." ### 7.3 Action handlers -One handler per `ActionDefinition.type`. Receives `(ctx, args)`, returns -a dict matching `output_contract` (or matching the user-declared -`output_schema` for dynamic-output actions like `agent_task`). +One handler per `ActionDefinition.type`. Receives the validated `args` +dict and returns whatever the step's output validates against (a fixed +shape declared by tight actions, or a dynamic shape declared via +`output_schema` in the step params for `agent_task`). -Handlers handle their own credential resolution via `ctx.resolve_credentials`. -They do not know about retries, timeouts, or budget caps — those are the +Handlers do not know about retries or timeouts — those are the executor's concern. +In v1, handlers take `(args)` only. The `CallContext` parameter sketched +in §7.2's pseudo-code (caller user id, search space id, run id, +credential resolver) arrives with the unification layer redesign (§3); +v1's single action (`agent_task`) reads what it needs from app-level +configuration. + ### 7.4 Template engine #### Why it exists @@ -747,7 +705,7 @@ Three fields, per-automation defaults with optional per-step overrides: - `timeout_seconds`: integer Retries on: -- Capability handler exceptions +- Action handler exceptions - Output schema validation failures (for dynamic-output actions, the validation error is fed back to the LLM in the retry) @@ -755,12 +713,21 @@ Not retries: - `when:` evaluation failures (these are user errors, surface immediately) - Input validation failures (caught at dispatch, never reach the executor) -### Budget enforcement +### Budget enforcement *(deferred — not in v1)* -`budget_cap_usd` is per-run. The dispatcher refuses to enqueue if estimated -cost exceeds it. The executor kills the run if accumulated cost crosses it -mid-flight (the LLM ops handler reports tokens consumed back to the -executor between calls). +Future shape: `budget_cap_usd` on `Execution`, dispatcher refuses to +enqueue if estimated cost exceeds it, executor kills the run if +accumulated cost crosses it mid-flight (the LLM ops handler reports +tokens consumed back to the executor between calls). + +Prerequisites before this can land: +- Each action declares cost reporting (tokens × model price, API call + charges) — `ActionDefinition` has no such field today. +- `automation_runs.cost_usd` column + executor accumulates per step. +- A historical-cost ledger so pre-flight estimation can return useful + numbers (otherwise the dispatcher gate is guessing). + +Until all three exist, v1 has no surface for budget enforcement. ### On-failure handlers @@ -787,14 +754,13 @@ nightly Celery Beat task deletes expired artifacts). ### Duration classes and queue routing — deferred The original design routed runs to multiple Celery queues based on each -capability's declared `expected_duration_seconds`. v1 ships with **one -queue** (`automations_default`) and capabilities do not declare a -duration. Multi-queue routing returns when burst load on a single queue -actually justifies the operational complexity of independent worker -pools. +action's declared `expected_duration_seconds`. v1 ships with **one +queue** (`automations_default`) and actions do not declare a duration. +Multi-queue routing returns when burst load on a single queue actually +justifies the operational complexity of independent worker pools. Adding the second queue is a config change plus reintroducing -`expected_duration_seconds` on the `Capability` dataclass — both +`expected_duration_seconds` on the `ActionDefinition` dataclass — both mechanical, additive, and free of design rewrite. --- @@ -832,14 +798,16 @@ and an immutable run history. ### `automation_triggers` -| field | type | notes | -| --------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | -| `id` | int PK | | -| `automation_id` | FK | | -| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | -| `config` | jsonb | validated against trigger's `config_schema` | -| `enabled` | bool | | -| `last_fired_at` | timestamp | | +| field | type | notes | +| --------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------- | +| `id` | int PK | | +| `automation_id` | FK | | +| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | +| `params` | jsonb | trigger-type config, validated against trigger's `params_schema` | +| `static_inputs` | jsonb | per-attachment domain values merged into every run (static wins on collision) | +| `enabled` | bool | | +| `last_fired_at` | timestamp | | +| `next_fire_at` | timestamp / null | precomputed next fire moment for schedule triggers | `secret_hash` (for webhook bearer tokens) is **deferred to Phase 2** with the webhook trigger. @@ -853,8 +821,7 @@ the webhook trigger. | `trigger_id` | FK / null | null = manual via UI | | `status` | enum | `pending`, `running`, `succeeded`, `failed`, `cancelled`, `timed_out` | | `definition_snapshot` | jsonb | the definition as it was when this run fired | -| `trigger_payload` | jsonb | | -| `resolved_inputs` | jsonb | | +| `inputs` | jsonb | merged & validated inputs (trigger.static_inputs ∪ producer runtime data, static wins) | | `step_results` | jsonb | array of per-step results with timing | | `output` | jsonb / null | | | `artifacts` | jsonb | references to created artifacts | @@ -863,7 +830,7 @@ the webhook trigger. | `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | `cost_usd` (per-run accumulated cost) is **deferred** until at least one -v1 capability records token-level cost. When reintroduced it lands as a +action records token-level cost. When reintroduced it lands as a column-only migration. ### Deferred tables @@ -897,8 +864,8 @@ not "trusted authors only." User provides natural-language input. The Generator LLM is given: - The full schema set (input schema for definition, registry of action - types with their config_schemas, registry of trigger types, available - capabilities for this SearchSpace, list of allowed Jinja filters) + types with their params_schemas, registry of trigger types, list of + allowed Jinja filters) - A tool to list available connectors, channels, and other SearchSpace resources, so it doesn't invent names that don't exist - A few-shot set of examples @@ -918,13 +885,13 @@ Output: a structured proposal matching the automation definition schema. Server-side, before the proposal reaches the user: - Validate against JSON Schema (shape correctness) -- Verify every capability referenced exists in the registry (resource existence) +- Verify every action and trigger type referenced exists in the registry - Verify every connector/channel/resource referenced exists in this SearchSpace - Validate every template against the sandbox's allowlist (no underscore attributes, no unregistered filter names, length under cap) Failures here are deterministic errors, not warnings. A proposal that -references a non-existent capability or includes a template using +references a non-existent action or includes a template using `{{x.__class__}}` is rejected before the user sees it; the Generator is re-prompted with the validation error and asked to fix the proposal. @@ -947,7 +914,7 @@ produces two outputs for the user: - Action sequences that touch external systems without obvious benefit to the user - Cost estimates that seem high relative to the goal - - References to capabilities the user hasn't used before + - References to actions the user hasn't used before - Schedules tighter than 15 minutes (likely should be event triggers) The Review LLM is a **UX layer** that makes review actually useful. It is @@ -1009,33 +976,18 @@ always. surfsense_backend/app/ ├── automations/ # NEW: the engine │ ├── __init__.py -│ ├── models.py # SQLAlchemy models for 6 tables -│ ├── schemas.py # Pydantic schemas (definition envelope, etc.) +│ ├── persistence/ # SQLAlchemy models + enums for 3 tables +│ ├── schemas/ # Pydantic schemas (definition envelope, etc.) │ ├── routes.py # FastAPI router (/api/v1/automations) │ ├── service.py # CRUD + business logic -│ ├── dispatcher.py # trigger matching, cost check, run creation +│ ├── dispatcher.py # trigger matching, run creation │ ├── executor.py # the Celery task that runs a plan │ ├── templating.py # Jinja sandbox + filters │ ├── events.py # publish/subscribe for domain_events │ ├── filters.py # JSON filter grammar evaluator -│ ├── actions/ -│ │ ├── registry.py -│ │ ├── agent_task.py -│ │ ├── transform_data.py -│ │ ├── slack_post.py -│ │ ├── send_email.py -│ │ ├── notification.py -│ │ └── (more in Phase 5: podcast_generation, report_generation, ...) -│ ├── triggers/ -│ │ ├── registry.py -│ │ ├── schedule.py # Celery Beat hookup -│ │ ├── webhook.py # /fire endpoint -│ │ └── event.py # subscribes to domain_events -│ ├── capabilities/ -│ │ ├── registry.py -│ │ ├── native.py # native capability registrations -│ │ ├── mcp_harvester.py # registers MCP tools as capabilities (Phase 4) -│ │ └── (LLM ops registered alongside) +│ ├── registries/ # action and trigger registries +│ │ ├── actions/ # ActionDefinition + handler registration +│ │ └── triggers/ # TriggerDefinition │ └── nl/ # Phase 1 — primary user path │ ├── generator.py # Generator LLM │ ├── reviewer.py # Review LLM (summary + flagged items) @@ -1070,23 +1022,22 @@ automations in natural language. **Step 1 (current scope, this batch of commits):** - 3 tables (`automations`, `automation_triggers`, `automation_runs`) + Alembic migration -- Empty Capability, Action, Trigger registries (concrete entries land in - later steps when the consuming feature lands) +- Empty action and trigger registries under + `app/automations/registries/` (concrete entries land in later steps) - Pydantic schemas for the automation definition envelope, the two v1 - trigger configs (`schedule`, `manual`), and the one v1 action config - (`agent_task`) -- Module structure under `app/automations/` (data/, schemas/, + trigger params shapes (`schedule`, `manual`), and the one v1 action + params shape (`agent_task`) +- Module structure under `app/automations/` (persistence/, schemas/, registries/), fully isolated from the existing codebase **Step 2:** -- Register the `agent_task` action and the `schedule` / `manual` - triggers in the registries -- Capability registry populated with native deliverable-producing - capabilities (chosen when this step starts) +- The `agent_task` action handler and the `schedule` / `manual` triggers + registered in `app/automations/registries/`. Tool resolution for + `agent_task.params.tools` is opaque to the contract — the handler + decides what string identifiers it accepts and how they resolve. **Step 3:** -- Executor (single-queue Celery task) with retries, timeouts, budget - caps measured against `cost_usd` ledger on the run +- Executor (single-queue Celery task) with retries and timeouts - Template engine (Jinja sandbox + the v1 filter allowlist + runtime limits) - Manual "Run now" endpoint @@ -1122,19 +1073,23 @@ somewhere humans see, complex pipelines have proper error handling. **After Phase 3**: NL authoring is the polished primary surface; edit flows are conversational rather than form-only. -### Phase 4 — Event triggers +### Phase 4 — Event triggers + integration tooling - `domain_events` table and `events.py` module - Indexing pipeline publishes `connector.*` events (smallest change — just add publish calls to the existing flow) - Automations publish `automation.run.*` events on completion - `event` trigger with filter grammar -- MCP capability harvester (so MCP-backed events and tools both work) +- The unification layer redesign (see §3) — `CallContext`, scope + declarations, per-user authorization gating +- MCP integration on top of the unification layer (external tool servers + harvested into the shared catalog) **After Phase 4**: "do X when Y happens" automations work, including -automation-chaining through events. +automation-chaining through events; external MCP tools and SurfSense +actions share one vocabulary. ### Phase 5 — Wrapping existing features and sharing -- Wrap existing SurfSense capabilities as actions: `podcast_generation`, +- Wrap existing SurfSense features as actions: `podcast_generation`, `report_generation`, `indexing_sweep` - Artifact lifecycle implementation - `expected_duration_seconds` based queue routing (split `automations_long` @@ -1144,7 +1099,7 @@ automation-chaining through events. shift documented in §7.4's pre-Phase-5 gate - Cross-automation composition examples in the docs -**After Phase 5**: every existing SurfSense capability is automatable +**After Phase 5**: every existing SurfSense feature is automatable without any per-feature code, and automations can be shared between SearchSpaces and users. @@ -1156,13 +1111,12 @@ For reference — every decision made through the design process, in one place. ### Foundations -1. ✅ JSON Schema 2020-12 is the single schema language for everything +1. ✅ JSON Schema (draft 2020-12) is the single schema language for everything 2. ✅ Definition is the program; infrastructure is the interpreter 3. ✅ List of steps (not single action) in the plan, with `output_as` chaining -4. ✅ One capability registry serving native + MCP + LLM operations through the same interface -5. ✅ Capability IDs do not leak handler kind (`slack.post_message`, not `mcp.slack.post_message`) -6. ✅ Name-based resolution: definitions reference actions and capabilities by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. -7. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) +4. ⏸ Capability unification layer (one catalog shared by automations, agents, and future surfaces) — **deferred to post-v1** (see §3). v1 ships actions only. +5. ✅ Name-based resolution: definitions reference action and trigger types by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. +6. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) ### Trigger taxonomy 8. ✅ Three trigger types: `schedule`, `webhook`, `event` @@ -1183,7 +1137,7 @@ place. 19. ✅ No DAGs, no parallelism, no loops — composition via agent_task or events 20. ✅ `on_failure` part of execution policy from v1 21. ✅ Step-level retry and timeout overrides -22. ✅ Budget cap enforced pre-enqueue and mid-flight +22. ⏸ Budget cap enforced pre-enqueue and mid-flight — **deferred** until the cost ledger ships (see §8 Budget enforcement) ### Components 23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable @@ -1197,25 +1151,22 @@ place. 29. ✅ Automations publish run events for composability 30. ✅ Publish/subscribe behind interface — no direct table access elsewhere -### Capability storage -31. ✅ Native capabilities registered in-memory at startup from the codebase. Identical across all workers. -32. ⏸ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables — **deferred to Phase 4** -33. ⏸ MCP handler closures built lazily per worker from database state — **deferred to Phase 4** -34. ⏸ MCP server tool list re-harvested on a schedule — **deferred to Phase 4** -35. ⏸ MCP tools harvested into the capability registry at connection time — **deferred to Phase 4** -36. ⏸ Side effects inferred from MCP hints + naming + admin overrides — **deferred to Phase 4** -37. ⏸ MCP tools callable directly (no agent required) when caller knows args — **deferred to Phase 4** +### Capability unification — all deferred to post-v1 +31. ⏸ One shared catalog of "things this SurfSense instance can do" — **deferred**, see §3 +32. ⏸ Handler `CallContext` (caller user id, search space id, run id) — **deferred** with unification +33. ⏸ Per-capability scope declarations driving authorization — **deferred** with unification +34. ⏸ MCP integration on top of the unification layer (`mcp_connections`, `mcp_tools`, harvester) — **deferred to Phase 4** ### Credentials — all deferred to Phase 2 -38. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** -39. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** -40. ⏸ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment — **Phase 2** -41. ⏸ Tokens encrypted at rest; refresh handled automatically by `ActionContext.resolve_*_client` — **Phase 2** +35. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** +36. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** +37. ⏸ Credentials resolved per-call by the handler context, not pre-loaded into worker environment — **Phase 2** +38. ⏸ Tokens encrypted at rest; refresh handled automatically by the handler context — **Phase 2** -### v1-minimum (new lock) -v1. ✅ `Capability` is exactly five fields: `id`, `description`, `input_schema`, `output_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. -v2. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. -v3. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. +### v1-minimum +39. ✅ v1 ships actions only — no separate capability layer. `ActionDefinition` is five fields: `type`, `name`, `description`, `params_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. +40. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. +41. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. ### NL authoring 42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary @@ -1227,7 +1178,7 @@ v3. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing ret 48. ✅ NL drafts are transient storage, not a core table ### Data model -49. ✅ Six tables total — four for engine state, two for MCP persistence +49. ✅ v1 ships three tables (`automations`, `automation_triggers`, `automation_runs`). `domain_events` lands in Phase 3; `mcp_connections` and `mcp_tools` in Phase 4. 50. ✅ Run rows snapshot the definition (immutable history) 51. ✅ All entities scoped by `search_space_id` for RBAC 52. ✅ Editing an automation bumps `version`; existing runs unaffected @@ -1283,7 +1234,7 @@ Schemas spelled out concretely. Those follow mechanically from this plan. agent (cron and skills subsystems); n8n documentation on node types and workflow data model; the SurfSense repository and DeepWiki architecture notes (FastAPI + Celery Beat + Electric SQL + LangGraph Deep Agents + -Search Space RBAC); Model Context Protocol specification for capability -harvesting; AWS EventBridge for filter grammar; workflow-pattern +Search Space RBAC); Model Context Protocol specification for external +tool harvesting; AWS EventBridge for filter grammar; workflow-pattern literature (van der Aalst et al.) for the trigger / action / concurrency vocabulary.* diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 6daf4075f..8d836095d 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -87,6 +87,7 @@ def upgrade() -> None: REFERENCES automations(id) ON DELETE CASCADE, type automation_trigger_type NOT NULL, params JSONB NOT NULL, + static_inputs JSONB NOT NULL DEFAULT '{}'::jsonb, enabled BOOLEAN NOT NULL DEFAULT true, last_fired_at TIMESTAMP WITH TIME ZONE, next_fire_at TIMESTAMP WITH TIME ZONE, @@ -129,8 +130,7 @@ def upgrade() -> None: REFERENCES automation_triggers(id) ON DELETE SET NULL, status automation_run_status NOT NULL DEFAULT 'pending', definition_snapshot JSONB NOT NULL, - trigger_payload JSONB, - resolved_inputs JSONB NOT NULL DEFAULT '{}'::jsonb, + inputs JSONB NOT NULL DEFAULT '{}'::jsonb, step_results JSONB NOT NULL DEFAULT '[]'::jsonb, output JSONB, artifacts JSONB NOT NULL DEFAULT '[]'::jsonb, diff --git a/surfsense_backend/app/automations/actions/agent_task/definition.py b/surfsense_backend/app/automations/actions/agent_task/definition.py index d7db5cfcd..7d14dc49e 100644 --- a/surfsense_backend/app/automations/actions/agent_task/definition.py +++ b/surfsense_backend/app/automations/actions/agent_task/definition.py @@ -11,7 +11,7 @@ AGENT_TASK_ACTION = ActionDefinition( type="agent_task", name="Agent task", description="Run a multi_agent_chat turn from an automation step.", - params_schema=AgentTaskActionParams.model_json_schema(), + params_model=AgentTaskActionParams, build_handler=build_handler, ) diff --git a/surfsense_backend/app/automations/actions/types.py b/surfsense_backend/app/automations/actions/types.py index 433c60841..2c4ffad8d 100644 --- a/surfsense_backend/app/automations/actions/types.py +++ b/surfsense_backend/app/automations/actions/types.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from typing import Any from uuid import UUID +from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession @@ -30,5 +31,10 @@ class ActionDefinition: type: str name: str description: str - params_schema: dict[str, Any] + params_model: type[BaseModel] build_handler: ActionHandlerFactory + + @property + def params_schema(self) -> dict[str, Any]: + """JSON Schema (draft 2020-12) derived from ``params_model``.""" + return self.params_model.model_json_schema() diff --git a/surfsense_backend/app/automations/api/__init__.py b/surfsense_backend/app/automations/api/__init__.py index 459c6c1b4..a18e91a95 100644 --- a/surfsense_backend/app/automations/api/__init__.py +++ b/surfsense_backend/app/automations/api/__init__.py @@ -5,8 +5,12 @@ from __future__ import annotations from fastapi import APIRouter from .automation import router as automation_router +from .run import router as run_router +from .trigger import router as trigger_router router = APIRouter() router.include_router(automation_router) +router.include_router(trigger_router) +router.include_router(run_router) __all__ = ["router"] diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py index 4d0ce7209..b67f0af09 100644 --- a/surfsense_backend/app/automations/api/automation.py +++ b/surfsense_backend/app/automations/api/automation.py @@ -1,23 +1,80 @@ -"""Routes for the ``Automation`` resource.""" +"""HTTP routes for the ``Automation`` resource.""" from __future__ import annotations -from typing import Any +from fastapi import APIRouter, Depends, Query, status -from fastapi import APIRouter, Body, Depends - -from app.automations.api.schemas import RunDispatched +from app.automations.schemas.api import ( + AutomationCreate, + AutomationDetail, + AutomationList, + AutomationSummary, + AutomationUpdate, +) from app.automations.services import AutomationService, get_automation_service router = APIRouter() -@router.post("/automations/{automation_id}/run", response_model=RunDispatched) -async def run_automation_now( - automation_id: int, - payload: dict[str, Any] | None = Body(default=None), +@router.post( + "/automations", + response_model=AutomationDetail, + status_code=status.HTTP_201_CREATED, +) +async def create_automation( + payload: AutomationCreate, service: AutomationService = Depends(get_automation_service), -) -> RunDispatched: - """Fire a manual run.""" - run = await service.run_now(automation_id=automation_id, payload=payload) - return RunDispatched(run_id=run.id, status=run.status) +) -> AutomationDetail: + """Create an automation, optionally with initial triggers (atomic).""" + automation = await service.create(payload) + return AutomationDetail.model_validate(automation) + + +@router.get("/automations", response_model=AutomationList) +async def list_automations( + search_space_id: int = Query(...), + limit: int = Query(default=50, ge=1, le=200), + offset: int = Query(default=0, ge=0), + service: AutomationService = Depends(get_automation_service), +) -> AutomationList: + """List automations in a search space.""" + items, total = await service.list( + search_space_id=search_space_id, limit=limit, offset=offset + ) + return AutomationList( + items=[AutomationSummary.model_validate(a) for a in items], + total=total, + ) + + +@router.get("/automations/{automation_id}", response_model=AutomationDetail) +async def get_automation( + automation_id: int, + service: AutomationService = Depends(get_automation_service), +) -> AutomationDetail: + """Get one automation with its definition and triggers.""" + automation = await service.get(automation_id) + return AutomationDetail.model_validate(automation) + + +@router.patch("/automations/{automation_id}", response_model=AutomationDetail) +async def update_automation( + automation_id: int, + patch: AutomationUpdate, + service: AutomationService = Depends(get_automation_service), +) -> AutomationDetail: + """Partially update an automation. Triggers are managed separately.""" + automation = await service.update(automation_id, patch) + return AutomationDetail.model_validate(automation) + + +@router.delete( + "/automations/{automation_id}", + status_code=status.HTTP_204_NO_CONTENT, +) +async def delete_automation( + automation_id: int, + service: AutomationService = Depends(get_automation_service), +) -> None: + """Delete an automation; triggers and runs are removed by FK cascade.""" + await service.delete(automation_id) diff --git a/surfsense_backend/app/automations/api/run.py b/surfsense_backend/app/automations/api/run.py new file mode 100644 index 000000000..d0d4bbfb7 --- /dev/null +++ b/surfsense_backend/app/automations/api/run.py @@ -0,0 +1,71 @@ +"""HTTP routes for automation runs (dispatch + history).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, Depends, Query, status + +from app.automations.schemas.api import ( + RunDetail, + RunDispatched, + RunList, + RunSummary, +) +from app.automations.services import RunService, get_run_service + +router = APIRouter() + + +@router.post( + "/automations/{automation_id}/run", + response_model=RunDispatched, + status_code=status.HTTP_202_ACCEPTED, +) +async def run_automation_now( + automation_id: int, + inputs: dict[str, Any] | None = Body(default=None), + service: RunService = Depends(get_run_service), +) -> RunDispatched: + """Fire a manual run. + + ``inputs`` is the runtime payload supplied by the caller; it is merged with + the manual trigger's ``static_inputs`` (static wins) and validated against + the automation's input schema. + """ + run = await service.dispatch_manual(automation_id=automation_id, runtime_inputs=inputs) + return RunDispatched(run_id=run.id, status=run.status) + + +@router.get( + "/automations/{automation_id}/runs", + response_model=RunList, +) +async def list_runs( + automation_id: int, + limit: int = Query(default=50, ge=1, le=200), + offset: int = Query(default=0, ge=0), + service: RunService = Depends(get_run_service), +) -> RunList: + """List run history for an automation, newest first.""" + items, total = await service.list( + automation_id=automation_id, limit=limit, offset=offset + ) + return RunList( + items=[RunSummary.model_validate(r) for r in items], + total=total, + ) + + +@router.get( + "/automations/{automation_id}/runs/{run_id}", + response_model=RunDetail, +) +async def get_run( + automation_id: int, + run_id: int, + service: RunService = Depends(get_run_service), +) -> RunDetail: + """Get the full record of a single run, including step results and artifacts.""" + run = await service.get(automation_id=automation_id, run_id=run_id) + return RunDetail.model_validate(run) diff --git a/surfsense_backend/app/automations/api/trigger.py b/surfsense_backend/app/automations/api/trigger.py new file mode 100644 index 000000000..40e47a86b --- /dev/null +++ b/surfsense_backend/app/automations/api/trigger.py @@ -0,0 +1,55 @@ +"""HTTP routes for triggers attached to an automation.""" + +from __future__ import annotations + +from fastapi import APIRouter, Depends, status + +from app.automations.schemas.api import TriggerCreate, TriggerDetail, TriggerUpdate +from app.automations.services import TriggerService, get_trigger_service + +router = APIRouter() + + +@router.post( + "/automations/{automation_id}/triggers", + response_model=TriggerDetail, + status_code=status.HTTP_201_CREATED, +) +async def add_trigger( + automation_id: int, + payload: TriggerCreate, + service: TriggerService = Depends(get_trigger_service), +) -> TriggerDetail: + """Attach a new trigger to an automation.""" + trigger = await service.add(automation_id=automation_id, payload=payload) + return TriggerDetail.model_validate(trigger) + + +@router.patch( + "/automations/{automation_id}/triggers/{trigger_id}", + response_model=TriggerDetail, +) +async def update_trigger( + automation_id: int, + trigger_id: int, + patch: TriggerUpdate, + service: TriggerService = Depends(get_trigger_service), +) -> TriggerDetail: + """Toggle ``enabled`` or replace ``params``. Trigger type is immutable.""" + trigger = await service.update( + automation_id=automation_id, trigger_id=trigger_id, patch=patch + ) + return TriggerDetail.model_validate(trigger) + + +@router.delete( + "/automations/{automation_id}/triggers/{trigger_id}", + status_code=status.HTTP_204_NO_CONTENT, +) +async def remove_trigger( + automation_id: int, + trigger_id: int, + service: TriggerService = Depends(get_trigger_service), +) -> None: + """Detach a trigger from an automation.""" + await service.remove(automation_id=automation_id, trigger_id=trigger_id) diff --git a/surfsense_backend/app/automations/dispatch/run.py b/surfsense_backend/app/automations/dispatch/run.py index fd5107a18..e317a13b9 100644 --- a/surfsense_backend/app/automations/dispatch/run.py +++ b/surfsense_backend/app/automations/dispatch/run.py @@ -22,10 +22,14 @@ async def dispatch_run( session: AsyncSession, automation: Automation, trigger: AutomationTrigger, - payload: dict[str, Any] | None, + runtime_inputs: dict[str, Any] | None = None, ) -> AutomationRun: """Validate, snapshot the definition, persist an ``AutomationRun``, enqueue execution. + Final inputs = ``trigger.static_inputs`` merged with ``runtime_inputs``, + static winning on key collision. The merged dict is validated against + ``automation.definition.inputs.schema_`` and stored on the run. + Callers (trigger-specific adapters) are responsible for resolving ``automation`` and ``trigger`` and for the trigger-side ``ACTIVE`` / ``enabled`` guards. This function only handles what's identical across @@ -36,7 +40,8 @@ async def dispatch_run( except Exception as exc: raise DispatchError(f"invalid automation definition: {exc}") from exc - resolved_inputs = _validate_inputs(definition, payload or {}) + merged_inputs = {**(runtime_inputs or {}), **(trigger.static_inputs or {})} + validated_inputs = _validate_inputs(definition, merged_inputs) snapshot = definition.model_dump(mode="json", by_alias=True) run = AutomationRun( @@ -44,8 +49,7 @@ async def dispatch_run( trigger_id=trigger.id, status=RunStatus.PENDING, definition_snapshot=snapshot, - trigger_payload=payload, - resolved_inputs=resolved_inputs, + inputs=validated_inputs, step_results=[], artifacts=[], ) @@ -61,12 +65,12 @@ async def dispatch_run( def _validate_inputs( - definition: AutomationDefinition, payload: dict[str, Any] + definition: AutomationDefinition, inputs: dict[str, Any] ) -> dict[str, Any]: if definition.inputs is None or not definition.inputs.schema_: return {} try: - jsonschema.validate(instance=payload, schema=definition.inputs.schema_) + jsonschema.validate(instance=inputs, schema=definition.inputs.schema_) except jsonschema.ValidationError as exc: raise DispatchError(f"inputs: {exc.message}") from exc - return payload + return inputs diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index fdc355e8f..81b33c37c 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -45,8 +45,9 @@ class AutomationRun(BaseModel, TimestampMixin): # locked at fire time so historical runs always show the exact code path definition_snapshot = Column(JSONB, nullable=False) - trigger_payload = Column(JSONB, nullable=True) - resolved_inputs = Column(JSONB, nullable=False, server_default="{}") + # merged & validated inputs the run was dispatched with + # (trigger.static_inputs ∪ producer runtime data, static wins on collision) + inputs = Column(JSONB, nullable=False, server_default="{}") # one entry per executed step; agent_task entries carry their own # `agent_session_id` inside their entry step_results = Column(JSONB, nullable=False, server_default="[]") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index b09bc3419..72d1d8d07 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -36,6 +36,10 @@ class AutomationTrigger(BaseModel, TimestampMixin): params = Column(JSONB, nullable=False) + # Per-attachment domain values merged into every dispatched run's inputs. + # Static wins over runtime data on key collision. + static_inputs = Column(JSONB, nullable=False, server_default="{}") + enabled = Column( Boolean, nullable=False, diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index ced44fb9b..b8a377e5b 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -106,7 +106,7 @@ def _build_template_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dic trigger_type=trigger.type.value if trigger else None, started_at=run.started_at, attempt=1, - resolved_inputs=run.resolved_inputs or {}, + inputs=run.inputs or {}, step_outputs=step_outputs, ) diff --git a/surfsense_backend/app/automations/api/schemas/__init__.py b/surfsense_backend/app/automations/schemas/api/__init__.py similarity index 100% rename from surfsense_backend/app/automations/api/schemas/__init__.py rename to surfsense_backend/app/automations/schemas/api/__init__.py diff --git a/surfsense_backend/app/automations/api/schemas/automation.py b/surfsense_backend/app/automations/schemas/api/automation.py similarity index 100% rename from surfsense_backend/app/automations/api/schemas/automation.py rename to surfsense_backend/app/automations/schemas/api/automation.py diff --git a/surfsense_backend/app/automations/api/schemas/run.py b/surfsense_backend/app/automations/schemas/api/run.py similarity index 92% rename from surfsense_backend/app/automations/api/schemas/run.py rename to surfsense_backend/app/automations/schemas/api/run.py index 789b6f674..42ea7ac14 100644 --- a/surfsense_backend/app/automations/api/schemas/run.py +++ b/surfsense_backend/app/automations/schemas/api/run.py @@ -28,8 +28,7 @@ class RunDetail(RunSummary): """Full run view including snapshot, results and artifacts.""" definition_snapshot: dict[str, Any] - trigger_payload: dict[str, Any] | None = None - resolved_inputs: dict[str, Any] + inputs: dict[str, Any] step_results: list[dict[str, Any]] output: dict[str, Any] | None = None artifacts: list[dict[str, Any]] diff --git a/surfsense_backend/app/automations/api/schemas/trigger.py b/surfsense_backend/app/automations/schemas/api/trigger.py similarity index 87% rename from surfsense_backend/app/automations/api/schemas/trigger.py rename to surfsense_backend/app/automations/schemas/api/trigger.py index 32afe7c60..35176fb9f 100644 --- a/surfsense_backend/app/automations/api/schemas/trigger.py +++ b/surfsense_backend/app/automations/schemas/api/trigger.py @@ -17,6 +17,7 @@ class TriggerCreate(BaseModel): type: TriggerType params: dict[str, Any] = Field(default_factory=dict) + static_inputs: dict[str, Any] = Field(default_factory=dict) enabled: bool = True @@ -27,6 +28,7 @@ class TriggerUpdate(BaseModel): enabled: bool | None = None params: dict[str, Any] | None = None + static_inputs: dict[str, Any] | None = None class TriggerDetail(BaseModel): @@ -37,6 +39,7 @@ class TriggerDetail(BaseModel): id: int type: TriggerType params: dict[str, Any] + static_inputs: dict[str, Any] enabled: bool last_fired_at: datetime | None = None next_fire_at: datetime | None = None diff --git a/surfsense_backend/app/automations/services/__init__.py b/surfsense_backend/app/automations/services/__init__.py index f0a97d216..597aca98a 100644 --- a/surfsense_backend/app/automations/services/__init__.py +++ b/surfsense_backend/app/automations/services/__init__.py @@ -1,7 +1,16 @@ -"""Service layer for the automations feature.""" +"""Services for the automations HTTP layer (one service per resource).""" from __future__ import annotations from .automation import AutomationService, get_automation_service +from .run import RunService, get_run_service +from .trigger import TriggerService, get_trigger_service -__all__ = ["AutomationService", "get_automation_service"] +__all__ = [ + "AutomationService", + "RunService", + "TriggerService", + "get_automation_service", + "get_run_service", + "get_trigger_service", +] diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py index 2a921e331..9140da3b5 100644 --- a/surfsense_backend/app/automations/services/automation.py +++ b/surfsense_backend/app/automations/services/automation.py @@ -2,54 +2,111 @@ from __future__ import annotations -from typing import Any +from datetime import UTC, datetime from fastapi import Depends, HTTPException +from pydantic import ValidationError +from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload -from app.automations.dispatch import DispatchError +from app.automations.schemas.api import ( + AutomationCreate, + AutomationUpdate, + TriggerCreate, +) +from app.automations.persistence.enums.trigger_type import TriggerType from app.automations.persistence.models.automation import Automation -from app.automations.persistence.models.run import AutomationRun -from app.automations.triggers.manual import dispatch_manual_run +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.triggers import get_trigger +from app.automations.triggers.schedule import compute_next_fire_at from app.db import Permission, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission class AutomationService: - """Service for the ``Automation`` resource.""" + """Lifecycle of the ``Automation`` resource.""" def __init__(self, *, session: AsyncSession, user: User) -> None: self.session = session self.user = user - async def run_now( + async def create(self, payload: AutomationCreate) -> Automation: + """Create an automation and its initial triggers in one transaction.""" + await self._authorize(payload.search_space_id, Permission.AUTOMATIONS_CREATE.value) + + automation = Automation( + search_space_id=payload.search_space_id, + created_by_user_id=self.user.id, + name=payload.name, + description=payload.description, + definition=payload.definition.model_dump(mode="json", by_alias=True), + version=1, + ) + for spec in payload.triggers: + automation.triggers.append(_build_trigger(spec)) + + self.session.add(automation) + await self.session.commit() + return await self._get_with_triggers_or_raise(automation.id) + + async def list( self, *, - automation_id: int, - payload: dict[str, Any] | None, - ) -> AutomationRun: - """Fire a manual run for ``automation_id``.""" - automation = await self._get_automation_or_raise(automation_id) - await check_permission( - self.session, - self.user, - automation.search_space_id, - Permission.AUTOMATIONS_EXECUTE.value, - "You don't have permission to execute automations in this search space", + search_space_id: int, + limit: int, + offset: int, + ) -> tuple[list[Automation], int]: + """Return a page of automations and the total count.""" + await self._authorize(search_space_id, Permission.AUTOMATIONS_READ.value) + + base = select(Automation).where(Automation.search_space_id == search_space_id) + total = await self.session.scalar( + select(func.count()).select_from(base.subquery()) ) - try: - return await dispatch_manual_run( - session=self.session, - automation_id=automation_id, - payload=payload, + rows = ( + await self.session.execute( + base.order_by(Automation.created_at.desc()).limit(limit).offset(offset) ) - except DispatchError as exc: - raise HTTPException(status_code=422, detail=str(exc)) from exc + ).scalars().all() + return list(rows), int(total or 0) - async def _get_automation_or_raise(self, automation_id: int) -> Automation: - """Get the automation by id; 404 if missing.""" + async def get(self, automation_id: int) -> Automation: + """Get an automation with its triggers loaded.""" + automation = await self._get_with_triggers_or_raise(automation_id) + await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_READ.value) + return automation + + async def update(self, automation_id: int, patch: AutomationUpdate) -> Automation: + """Patch fields. Bumps ``version`` when ``definition`` changes.""" + automation = await self._get_with_triggers_or_raise(automation_id) + await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_UPDATE.value) + + data = patch.model_dump(exclude_unset=True) + + if "name" in data: + automation.name = data["name"] + if "description" in data: + automation.description = data["description"] + if "status" in data: + automation.status = data["status"] + if "definition" in data: + automation.definition = patch.definition.model_dump(mode="json", by_alias=True) + automation.version += 1 + + await self.session.commit() + return await self._get_with_triggers_or_raise(automation_id) + + async def delete(self, automation_id: int) -> None: + """Delete an automation; FK cascades remove triggers and runs.""" + automation = await self._get_or_raise(automation_id) + await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_DELETE.value) + await self.session.delete(automation) + await self.session.commit() + + async def _get_or_raise(self, automation_id: int) -> Automation: automation = await self.session.get(Automation, automation_id) if automation is None: raise HTTPException( @@ -57,6 +114,56 @@ class AutomationService: ) return automation + async def _get_with_triggers_or_raise(self, automation_id: int) -> Automation: + stmt = ( + select(Automation) + .where(Automation.id == automation_id) + .options(selectinload(Automation.triggers)) + ) + automation = (await self.session.execute(stmt)).scalar_one_or_none() + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + return automation + + async def _authorize(self, search_space_id: int, permission: str) -> None: + await check_permission( + self.session, + self.user, + search_space_id, + permission, + f"You don't have permission to {permission.split(':')[1]} automations in this search space", + ) + + +def _build_trigger(spec: TriggerCreate) -> AutomationTrigger: + """Validate trigger params via its registered Pydantic model and build the ORM row.""" + definition = get_trigger(spec.type.value) + if definition is None: + raise HTTPException(status_code=422, detail=f"unknown trigger type {spec.type.value!r}") + + try: + validated = definition.params_model.model_validate(spec.params) + except ValidationError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + params = validated.model_dump(mode="json") + + next_fire_at = None + if spec.type == TriggerType.SCHEDULE and spec.enabled: + next_fire_at = compute_next_fire_at( + params["cron"], params["timezone"], after=datetime.now(UTC) + ) + + return AutomationTrigger( + type=spec.type, + params=params, + static_inputs=spec.static_inputs, + enabled=spec.enabled, + next_fire_at=next_fire_at, + ) + def get_automation_service( session: AsyncSession = Depends(get_async_session), diff --git a/surfsense_backend/app/automations/services/run.py b/surfsense_backend/app/automations/services/run.py new file mode 100644 index 000000000..92d79e9bc --- /dev/null +++ b/surfsense_backend/app/automations/services/run.py @@ -0,0 +1,93 @@ +"""``RunService`` — dispatch and history of automation runs.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import Depends, HTTPException +from sqlalchemy import func, select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.triggers.manual import dispatch_manual_run +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + + +class RunService: + """Lifecycle of the ``AutomationRun`` resource.""" + + def __init__(self, *, session: AsyncSession, user: User) -> None: + self.session = session + self.user = user + + async def dispatch_manual( + self, + *, + automation_id: int, + runtime_inputs: dict[str, Any] | None, + ) -> AutomationRun: + """Fire a manual run via the registered manual trigger.""" + await self._authorize(automation_id, Permission.AUTOMATIONS_EXECUTE.value) + try: + return await dispatch_manual_run( + session=self.session, + automation_id=automation_id, + runtime_inputs=runtime_inputs, + ) + except DispatchError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + async def list( + self, + *, + automation_id: int, + limit: int, + offset: int, + ) -> tuple[list[AutomationRun], int]: + """Return a page of runs for an automation, newest first.""" + await self._authorize(automation_id, Permission.AUTOMATIONS_READ.value) + + base = select(AutomationRun).where(AutomationRun.automation_id == automation_id) + total = await self.session.scalar( + select(func.count()).select_from(base.subquery()) + ) + + rows = ( + await self.session.execute( + base.order_by(AutomationRun.created_at.desc()).limit(limit).offset(offset) + ) + ).scalars().all() + return list(rows), int(total or 0) + + async def get(self, *, automation_id: int, run_id: int) -> AutomationRun: + await self._authorize(automation_id, Permission.AUTOMATIONS_READ.value) + run = await self.session.get(AutomationRun, run_id) + if run is None or run.automation_id != automation_id: + raise HTTPException(status_code=404, detail=f"run {run_id} not found") + return run + + async def _authorize(self, automation_id: int, permission: str) -> Automation: + automation = await self.session.get(Automation, automation_id) + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + await check_permission( + self.session, + self.user, + automation.search_space_id, + permission, + f"You don't have permission to {permission.split(':')[1]} automations in this search space", + ) + return automation + + +def get_run_service( + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> RunService: + return RunService(session=session, user=user) diff --git a/surfsense_backend/app/automations/services/trigger.py b/surfsense_backend/app/automations/services/trigger.py new file mode 100644 index 000000000..33e9c1386 --- /dev/null +++ b/surfsense_backend/app/automations/services/trigger.py @@ -0,0 +1,143 @@ +"""``TriggerService`` — lifecycle of triggers attached to an automation.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +from fastapi import Depends, HTTPException +from pydantic import ValidationError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.schemas.api import TriggerCreate, TriggerUpdate +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.triggers import get_trigger +from app.automations.triggers.schedule import compute_next_fire_at +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + + +class TriggerService: + """Lifecycle of the ``AutomationTrigger`` sub-resource.""" + + def __init__(self, *, session: AsyncSession, user: User) -> None: + self.session = session + self.user = user + + async def add( + self, *, automation_id: int, payload: TriggerCreate + ) -> AutomationTrigger: + automation = await self._authorize_automation( + automation_id, Permission.AUTOMATIONS_UPDATE.value + ) + + validated_params = _validate_params(payload.type, payload.params) + trigger = AutomationTrigger( + automation_id=automation.id, + type=payload.type, + params=validated_params, + static_inputs=payload.static_inputs, + enabled=payload.enabled, + next_fire_at=_initial_next_fire(payload.type, validated_params, payload.enabled), + ) + self.session.add(trigger) + await self.session.commit() + await self.session.refresh(trigger) + return trigger + + async def update( + self, + *, + automation_id: int, + trigger_id: int, + patch: TriggerUpdate, + ) -> AutomationTrigger: + await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + trigger = await self._get_trigger_or_raise(automation_id, trigger_id) + + data = patch.model_dump(exclude_unset=True) + + if "params" in data: + trigger.params = _validate_params(trigger.type, data["params"]) + + if "static_inputs" in data: + trigger.static_inputs = data["static_inputs"] + + if "enabled" in data: + trigger.enabled = data["enabled"] + + # Recompute next_fire_at when schedule timing changed or the trigger was + # toggled back on. Manual triggers always have NULL next_fire_at. + if trigger.type == TriggerType.SCHEDULE: + trigger.next_fire_at = _initial_next_fire( + trigger.type, trigger.params, trigger.enabled + ) + + await self.session.commit() + await self.session.refresh(trigger) + return trigger + + async def remove(self, *, automation_id: int, trigger_id: int) -> None: + await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + trigger = await self._get_trigger_or_raise(automation_id, trigger_id) + await self.session.delete(trigger) + await self.session.commit() + + async def _authorize_automation( + self, automation_id: int, permission: str + ) -> Automation: + automation = await self.session.get(Automation, automation_id) + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + await check_permission( + self.session, + self.user, + automation.search_space_id, + permission, + f"You don't have permission to {permission.split(':')[1]} automations in this search space", + ) + return automation + + async def _get_trigger_or_raise( + self, automation_id: int, trigger_id: int + ) -> AutomationTrigger: + trigger = await self.session.get(AutomationTrigger, trigger_id) + if trigger is None or trigger.automation_id != automation_id: + raise HTTPException( + status_code=404, detail=f"trigger {trigger_id} not found" + ) + return trigger + + +def _validate_params(trigger_type: TriggerType, raw: dict) -> dict: + definition = get_trigger(trigger_type.value) + if definition is None: + raise HTTPException( + status_code=422, detail=f"unknown trigger type {trigger_type.value!r}" + ) + try: + validated = definition.params_model.model_validate(raw) + except ValidationError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + return validated.model_dump(mode="json") + + +def _initial_next_fire( + trigger_type: TriggerType, params: dict, enabled: bool +) -> datetime | None: + if trigger_type != TriggerType.SCHEDULE or not enabled: + return None + return compute_next_fire_at( + params["cron"], params["timezone"], after=datetime.now(UTC) + ) + + +def get_trigger_service( + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> TriggerService: + return TriggerService(session=session, user=user) diff --git a/surfsense_backend/app/automations/tasks/schedule_tick.py b/surfsense_backend/app/automations/tasks/schedule_tick.py index cade621c7..385bd7242 100644 --- a/surfsense_backend/app/automations/tasks/schedule_tick.py +++ b/surfsense_backend/app/automations/tasks/schedule_tick.py @@ -15,6 +15,7 @@ Runs every minute. Each tick performs two passes: from __future__ import annotations import logging +from dataclasses import dataclass from datetime import UTC, datetime from sqlalchemy import select @@ -39,6 +40,15 @@ TASK_NAME = "automation_schedule_tick" _TICK_BATCH = 200 +@dataclass(frozen=True, slots=True) +class _Claim: + """Per-trigger fire context captured before row state is mutated.""" + + trigger_id: int + scheduled_for: datetime + previous_last_fired_at: datetime | None + + @celery_app.task(name=TASK_NAME) def automation_schedule_tick() -> None: """Tick once: self-heal NULL next_fire_at, claim due rows, fire each.""" @@ -52,12 +62,12 @@ async def _tick() -> None: await _self_heal_null_next_fire(session, now=now) - claimed_ids = await _claim_due_triggers(session, now=now) - if not claimed_ids: + claims = await _claim_due_triggers(session, now=now) + if not claims: return - for trigger_id in claimed_ids: - await _fire_one(session, trigger_id=trigger_id) + for claim in claims: + await _fire_one(session, claim=claim, fired_at=now) async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> None: @@ -95,8 +105,8 @@ async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> async def _claim_due_triggers( session: AsyncSession, *, now: datetime -) -> list[int]: - """Lock and advance due rows; return claimed trigger ids.""" +) -> list[_Claim]: + """Lock and advance due rows; return per-trigger fire context.""" stmt = ( select(AutomationTrigger) .where( @@ -113,8 +123,12 @@ async def _claim_due_triggers( if not triggers: return [] - claimed: list[int] = [] + claims: list[_Claim] = [] for trigger in triggers: + # Snapshot fire-context BEFORE we advance the row. + scheduled_for = trigger.next_fire_at + previous_last_fired_at = trigger.last_fired_at + try: trigger.next_fire_at = compute_next_fire_at( trigger.params["cron"], @@ -131,29 +145,43 @@ async def _claim_due_triggers( continue trigger.last_fired_at = now - claimed.append(trigger.id) + claims.append( + _Claim( + trigger_id=trigger.id, + scheduled_for=scheduled_for, + previous_last_fired_at=previous_last_fired_at, + ) + ) await session.commit() - return claimed + return claims -async def _fire_one(session: AsyncSession, *, trigger_id: int) -> None: +async def _fire_one( + session: AsyncSession, *, claim: _Claim, fired_at: datetime +) -> None: """Reload the trigger post-commit and dispatch a run for it.""" - trigger = await session.get(AutomationTrigger, trigger_id) + trigger = await session.get(AutomationTrigger, claim.trigger_id) if trigger is None: return try: - run = await dispatch_schedule_run(session=session, trigger=trigger) + run = await dispatch_schedule_run( + session=session, + trigger=trigger, + fired_at=fired_at, + scheduled_for=claim.scheduled_for, + previous_last_fired_at=claim.previous_last_fired_at, + ) logger.info( "scheduled fire: trigger=%d automation=%d run=%d", - trigger_id, + claim.trigger_id, trigger.automation_id, run.id, ) except Exception: logger.exception( "scheduled fire failed for trigger %d (next attempt at next match)", - trigger_id, + claim.trigger_id, ) await session.rollback() diff --git a/surfsense_backend/app/automations/templating/context.py b/surfsense_backend/app/automations/templating/context.py index 3ca87694c..96fdb02e9 100644 --- a/surfsense_backend/app/automations/templating/context.py +++ b/surfsense_backend/app/automations/templating/context.py @@ -19,7 +19,7 @@ def build_run_context( trigger_type: str | None, started_at: datetime | None, attempt: int, - resolved_inputs: Mapping[str, Any], + inputs: Mapping[str, Any], step_outputs: Mapping[str, Any], ) -> dict[str, Any]: """Build the ``{run, inputs, steps}`` namespace exposed to every template.""" @@ -36,6 +36,6 @@ def build_run_context( "started_at": started_at, "attempt": attempt, }, - "inputs": dict(resolved_inputs), + "inputs": dict(inputs), "steps": dict(step_outputs), } diff --git a/surfsense_backend/app/automations/triggers/manual/definition.py b/surfsense_backend/app/automations/triggers/manual/definition.py index 9eb0282af..5a3529116 100644 --- a/surfsense_backend/app/automations/triggers/manual/definition.py +++ b/surfsense_backend/app/automations/triggers/manual/definition.py @@ -9,8 +9,7 @@ from .params import ManualTriggerParams MANUAL_TRIGGER = TriggerDefinition( type="manual", description="Fire on a user-initiated 'Run now' invocation.", - params_schema=ManualTriggerParams.model_json_schema(), - payload_schema={"type": "object"}, + params_model=ManualTriggerParams, ) register_trigger(MANUAL_TRIGGER) diff --git a/surfsense_backend/app/automations/triggers/manual/dispatch.py b/surfsense_backend/app/automations/triggers/manual/dispatch.py index 750c99937..6c92317d0 100644 --- a/surfsense_backend/app/automations/triggers/manual/dispatch.py +++ b/surfsense_backend/app/automations/triggers/manual/dispatch.py @@ -19,9 +19,14 @@ async def dispatch_manual_run( *, session: AsyncSession, automation_id: int, - payload: dict[str, Any] | None, + runtime_inputs: dict[str, Any] | None, ) -> AutomationRun: - """Find the automation + its enabled manual trigger, then run the generic dispatch.""" + """Find the automation + its enabled manual trigger, then run the generic dispatch. + + ``runtime_inputs`` is the caller-supplied payload (e.g. an HTTP body for a + "Run now" API call); it is merged with the trigger's ``static_inputs`` by + the generic dispatcher, with static winning on key collision. + """ automation = await _load_automation(session, automation_id) if automation is None: raise DispatchError(f"automation {automation_id} not found") @@ -41,7 +46,7 @@ async def dispatch_manual_run( session=session, automation=automation, trigger=trigger, - payload=payload, + runtime_inputs=runtime_inputs, ) diff --git a/surfsense_backend/app/automations/triggers/schedule/definition.py b/surfsense_backend/app/automations/triggers/schedule/definition.py index 3f86d767c..605765307 100644 --- a/surfsense_backend/app/automations/triggers/schedule/definition.py +++ b/surfsense_backend/app/automations/triggers/schedule/definition.py @@ -9,12 +9,7 @@ from .params import ScheduleTriggerParams SCHEDULE_TRIGGER = TriggerDefinition( type="schedule", description="Fire on a cron schedule in a given timezone.", - params_schema=ScheduleTriggerParams.model_json_schema(), - payload_schema={ - "type": "object", - "additionalProperties": False, - "properties": {}, - }, + params_model=ScheduleTriggerParams, ) register_trigger(SCHEDULE_TRIGGER) diff --git a/surfsense_backend/app/automations/triggers/schedule/dispatch.py b/surfsense_backend/app/automations/triggers/schedule/dispatch.py index fb4fcf686..6d3d5fcb9 100644 --- a/surfsense_backend/app/automations/triggers/schedule/dispatch.py +++ b/surfsense_backend/app/automations/triggers/schedule/dispatch.py @@ -2,6 +2,8 @@ from __future__ import annotations +from datetime import datetime + from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -16,9 +18,18 @@ async def dispatch_schedule_run( *, session: AsyncSession, trigger: AutomationTrigger, + fired_at: datetime, + scheduled_for: datetime, + previous_last_fired_at: datetime | None, ) -> AutomationRun: """Fire one scheduled run for ``trigger``. + Emits calendar context as runtime inputs: + + - ``fired_at`` — actual fire time + - ``scheduled_for`` — cron-derived target time for this fire + - ``last_fired_at`` — fire time of the previous run, or null on first fire + The caller (the schedule tick) is responsible for selecting due triggers and advancing ``next_fire_at`` / ``last_fired_at`` before invoking this. """ @@ -33,11 +44,19 @@ async def dispatch_schedule_run( f"automation {trigger.automation_id} is {automation.status.value}, not active" ) + runtime_inputs = { + "fired_at": fired_at.isoformat(), + "scheduled_for": scheduled_for.isoformat(), + "last_fired_at": ( + previous_last_fired_at.isoformat() if previous_last_fired_at else None + ), + } + return await dispatch_run( session=session, automation=automation, trigger=trigger, - payload=None, + runtime_inputs=runtime_inputs, ) diff --git a/surfsense_backend/app/automations/triggers/types.py b/surfsense_backend/app/automations/triggers/types.py index 783bd7842..aa2808e4d 100644 --- a/surfsense_backend/app/automations/triggers/types.py +++ b/surfsense_backend/app/automations/triggers/types.py @@ -5,10 +5,16 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from pydantic import BaseModel + @dataclass(frozen=True, slots=True) class TriggerDefinition: type: str description: str - params_schema: dict[str, Any] - payload_schema: dict[str, Any] + params_model: type[BaseModel] + + @property + def params_schema(self) -> dict[str, Any]: + """JSON Schema (draft 2020-12) derived from ``params_model``.""" + return self.params_model.model_json_schema() From 8fb65d7188c05d8193a211af4cfbf96d6b0327c4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 21:53:07 +0200 Subject: [PATCH 062/133] fix(automations): use enum values not names for postgres enum columns --- .../app/automations/persistence/models/automation.py | 6 +++++- surfsense_backend/app/automations/persistence/models/run.py | 6 +++++- .../app/automations/persistence/models/trigger.py | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py index ee86851c1..cb0b2ed31 100644 --- a/surfsense_backend/app/automations/persistence/models/automation.py +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -42,7 +42,11 @@ class Automation(BaseModel, TimestampMixin): description = Column(Text, nullable=True) status = Column( - SQLAlchemyEnum(AutomationStatus, name="automation_status"), + SQLAlchemyEnum( + AutomationStatus, + name="automation_status", + values_callable=lambda x: [e.value for e in x], + ), nullable=False, default=AutomationStatus.ACTIVE, server_default=AutomationStatus.ACTIVE.value, diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 81b33c37c..262e4c2bf 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -35,7 +35,11 @@ class AutomationRun(BaseModel, TimestampMixin): ) status = Column( - SQLAlchemyEnum(RunStatus, name="automation_run_status"), + SQLAlchemyEnum( + RunStatus, + name="automation_run_status", + values_callable=lambda x: [e.value for e in x], + ), nullable=False, default=RunStatus.PENDING, server_default=RunStatus.PENDING.value, diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 72d1d8d07..f73a8f350 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -29,7 +29,11 @@ class AutomationTrigger(BaseModel, TimestampMixin): ) type = Column( - SQLAlchemyEnum(TriggerType, name="automation_trigger_type"), + SQLAlchemyEnum( + TriggerType, + name="automation_trigger_type", + values_callable=lambda x: [e.value for e in x], + ), nullable=False, index=True, ) From c0232fdcfe346f4521c0b5aa578b3ac178bcdc6f Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 22:29:51 +0200 Subject: [PATCH 063/133] refactor(automations): park manual trigger pending Run-now redesign Manual-as-a-standalone-trigger conflates "user clicks Run now" with the trigger model and forces ad-hoc input plumbing on the caller. Remove the unreachable surface so the tree reflects reality (schedule is the only v1 trigger). - Unregister `manual`: drop import from triggers/__init__.py - Delete `app/automations/triggers/manual/` - Drop `RunService.dispatch_manual` (RunService is now read-only) - Drop `POST /automations/{id}/run` and `RunDispatched` schema - Keep `TriggerType.MANUAL` Python + PG enum value (reserved, documented) to avoid an Alembic round-trip when Run-now is redesigned --- surfsense_backend/app/automations/api/run.py | 33 +-------- .../persistence/enums/trigger_type.py | 7 +- .../automations/persistence/models/trigger.py | 2 +- .../app/automations/schemas/api/__init__.py | 3 +- .../app/automations/schemas/api/run.py | 9 +-- .../app/automations/services/run.py | 25 +------ .../app/automations/services/trigger.py | 2 +- .../app/automations/triggers/__init__.py | 4 +- .../automations/triggers/manual/__init__.py | 11 --- .../automations/triggers/manual/definition.py | 15 ---- .../automations/triggers/manual/dispatch.py | 72 ------------------- .../app/automations/triggers/manual/params.py | 9 --- surfsense_backend/app/routes/__init__.py | 2 +- 13 files changed, 18 insertions(+), 176 deletions(-) delete mode 100644 surfsense_backend/app/automations/triggers/manual/__init__.py delete mode 100644 surfsense_backend/app/automations/triggers/manual/definition.py delete mode 100644 surfsense_backend/app/automations/triggers/manual/dispatch.py delete mode 100644 surfsense_backend/app/automations/triggers/manual/params.py diff --git a/surfsense_backend/app/automations/api/run.py b/surfsense_backend/app/automations/api/run.py index d0d4bbfb7..b662a5943 100644 --- a/surfsense_backend/app/automations/api/run.py +++ b/surfsense_backend/app/automations/api/run.py @@ -1,42 +1,15 @@ -"""HTTP routes for automation runs (dispatch + history).""" +"""HTTP routes for automation run history.""" from __future__ import annotations -from typing import Any +from fastapi import APIRouter, Depends, Query -from fastapi import APIRouter, Body, Depends, Query, status - -from app.automations.schemas.api import ( - RunDetail, - RunDispatched, - RunList, - RunSummary, -) +from app.automations.schemas.api import RunDetail, RunList, RunSummary from app.automations.services import RunService, get_run_service router = APIRouter() -@router.post( - "/automations/{automation_id}/run", - response_model=RunDispatched, - status_code=status.HTTP_202_ACCEPTED, -) -async def run_automation_now( - automation_id: int, - inputs: dict[str, Any] | None = Body(default=None), - service: RunService = Depends(get_run_service), -) -> RunDispatched: - """Fire a manual run. - - ``inputs`` is the runtime payload supplied by the caller; it is merged with - the manual trigger's ``static_inputs`` (static wins) and validated against - the automation's input schema. - """ - run = await service.dispatch_manual(automation_id=automation_id, runtime_inputs=inputs) - return RunDispatched(run_id=run.id, status=run.status) - - @router.get( "/automations/{automation_id}/runs", response_model=RunList, diff --git a/surfsense_backend/app/automations/persistence/enums/trigger_type.py b/surfsense_backend/app/automations/persistence/enums/trigger_type.py index 8318bfdee..a583b1bd6 100644 --- a/surfsense_backend/app/automations/persistence/enums/trigger_type.py +++ b/surfsense_backend/app/automations/persistence/enums/trigger_type.py @@ -1,4 +1,9 @@ -"""Trigger-kind discriminator. v1: schedule | manual; webhook/event in Phase 2/3.""" +"""Trigger-kind discriminator. + +v1 only registers ``schedule``. ``manual`` is reserved in the enum (mirrors the +postgres enum) but is intentionally unregistered pending a redesign of the +"Run now" UX. +""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index f73a8f350..de1078acf 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -56,7 +56,7 @@ class AutomationTrigger(BaseModel, TimestampMixin): # Precomputed next fire moment in UTC; advanced after each fire by the # schedule tick. NULL means the trigger has never been scheduled (the - # tick self-heals on first sight). Manual triggers leave this NULL. + # tick self-heals on first sight). next_fire_at = Column(TIMESTAMP(timezone=True), nullable=True) automation = relationship("Automation", back_populates="triggers") diff --git a/surfsense_backend/app/automations/schemas/api/__init__.py b/surfsense_backend/app/automations/schemas/api/__init__.py index a8a010a2c..f49e5c589 100644 --- a/surfsense_backend/app/automations/schemas/api/__init__.py +++ b/surfsense_backend/app/automations/schemas/api/__init__.py @@ -9,7 +9,7 @@ from .automation import ( AutomationSummary, AutomationUpdate, ) -from .run import RunDetail, RunDispatched, RunList, RunSummary +from .run import RunDetail, RunList, RunSummary from .trigger import TriggerCreate, TriggerDetail, TriggerUpdate __all__ = [ @@ -19,7 +19,6 @@ __all__ = [ "AutomationSummary", "AutomationUpdate", "RunDetail", - "RunDispatched", "RunList", "RunSummary", "TriggerCreate", diff --git a/surfsense_backend/app/automations/schemas/api/run.py b/surfsense_backend/app/automations/schemas/api/run.py index 42ea7ac14..3f6eaab82 100644 --- a/surfsense_backend/app/automations/schemas/api/run.py +++ b/surfsense_backend/app/automations/schemas/api/run.py @@ -1,4 +1,4 @@ -"""Response schemas for run sub-resources and run dispatch.""" +"""Response schemas for run sub-resources.""" from __future__ import annotations @@ -40,10 +40,3 @@ class RunList(BaseModel): items: list[RunSummary] total: int - - -class RunDispatched(BaseModel): - """Response of a successful run dispatch.""" - - run_id: int - status: RunStatus diff --git a/surfsense_backend/app/automations/services/run.py b/surfsense_backend/app/automations/services/run.py index 92d79e9bc..ac9970241 100644 --- a/surfsense_backend/app/automations/services/run.py +++ b/surfsense_backend/app/automations/services/run.py @@ -1,46 +1,25 @@ -"""``RunService`` — dispatch and history of automation runs.""" +"""``RunService`` — read-only access to automation run history.""" from __future__ import annotations -from typing import Any - from fastapi import Depends, HTTPException from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from app.automations.dispatch import DispatchError from app.automations.persistence.models.automation import Automation from app.automations.persistence.models.run import AutomationRun -from app.automations.triggers.manual import dispatch_manual_run from app.db import Permission, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission class RunService: - """Lifecycle of the ``AutomationRun`` resource.""" + """Read-only access to ``AutomationRun`` history.""" def __init__(self, *, session: AsyncSession, user: User) -> None: self.session = session self.user = user - async def dispatch_manual( - self, - *, - automation_id: int, - runtime_inputs: dict[str, Any] | None, - ) -> AutomationRun: - """Fire a manual run via the registered manual trigger.""" - await self._authorize(automation_id, Permission.AUTOMATIONS_EXECUTE.value) - try: - return await dispatch_manual_run( - session=self.session, - automation_id=automation_id, - runtime_inputs=runtime_inputs, - ) - except DispatchError as exc: - raise HTTPException(status_code=422, detail=str(exc)) from exc - async def list( self, *, diff --git a/surfsense_backend/app/automations/services/trigger.py b/surfsense_backend/app/automations/services/trigger.py index 33e9c1386..c76cc0740 100644 --- a/surfsense_backend/app/automations/services/trigger.py +++ b/surfsense_backend/app/automations/services/trigger.py @@ -69,7 +69,7 @@ class TriggerService: trigger.enabled = data["enabled"] # Recompute next_fire_at when schedule timing changed or the trigger was - # toggled back on. Manual triggers always have NULL next_fire_at. + # toggled back on. if trigger.type == TriggerType.SCHEDULE: trigger.next_fire_at = _initial_next_fire( trigger.type, trigger.params, trigger.enabled diff --git a/surfsense_backend/app/automations/triggers/__init__.py b/surfsense_backend/app/automations/triggers/__init__.py index 258b2fda9..d7abb6b5d 100644 --- a/surfsense_backend/app/automations/triggers/__init__.py +++ b/surfsense_backend/app/automations/triggers/__init__.py @@ -1,6 +1,6 @@ """Triggers domain: registry surface + built-in trigger packages. -Each trigger lives in its own subpackage (``manual/``, ``schedule/``, ...) and +Each trigger lives in its own subpackage (``schedule/``, ...) and self-registers at import time via its ``definition`` module. """ @@ -17,4 +17,4 @@ __all__ = [ ] # Built-in triggers self-register at import time. -from . import manual, schedule # noqa: E402, F401 +from . import schedule # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/triggers/manual/__init__.py b/surfsense_backend/app/automations/triggers/manual/__init__.py deleted file mode 100644 index 65cca9270..000000000 --- a/surfsense_backend/app/automations/triggers/manual/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""``manual`` trigger: fired by a user clicking ``Run now``.""" - -from __future__ import annotations - -from .dispatch import dispatch_manual_run -from .params import ManualTriggerParams - -__all__ = ["ManualTriggerParams", "dispatch_manual_run"] - -# Side-effect: register on the triggers store. -from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/triggers/manual/definition.py b/surfsense_backend/app/automations/triggers/manual/definition.py deleted file mode 100644 index 5a3529116..000000000 --- a/surfsense_backend/app/automations/triggers/manual/definition.py +++ /dev/null @@ -1,15 +0,0 @@ -"""``manual`` ``TriggerDefinition`` registration.""" - -from __future__ import annotations - -from ..store import register_trigger -from ..types import TriggerDefinition -from .params import ManualTriggerParams - -MANUAL_TRIGGER = TriggerDefinition( - type="manual", - description="Fire on a user-initiated 'Run now' invocation.", - params_model=ManualTriggerParams, -) - -register_trigger(MANUAL_TRIGGER) diff --git a/surfsense_backend/app/automations/triggers/manual/dispatch.py b/surfsense_backend/app/automations/triggers/manual/dispatch.py deleted file mode 100644 index 6c92317d0..000000000 --- a/surfsense_backend/app/automations/triggers/manual/dispatch.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Manual ``Run now`` dispatch adapter: load + guard, then call generic dispatch.""" - -from __future__ import annotations - -from typing import Any - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.automations.dispatch import DispatchError, dispatch_run -from app.automations.persistence.enums.automation_status import AutomationStatus -from app.automations.persistence.enums.trigger_type import TriggerType -from app.automations.persistence.models.automation import Automation -from app.automations.persistence.models.run import AutomationRun -from app.automations.persistence.models.trigger import AutomationTrigger - - -async def dispatch_manual_run( - *, - session: AsyncSession, - automation_id: int, - runtime_inputs: dict[str, Any] | None, -) -> AutomationRun: - """Find the automation + its enabled manual trigger, then run the generic dispatch. - - ``runtime_inputs`` is the caller-supplied payload (e.g. an HTTP body for a - "Run now" API call); it is merged with the trigger's ``static_inputs`` by - the generic dispatcher, with static winning on key collision. - """ - automation = await _load_automation(session, automation_id) - if automation is None: - raise DispatchError(f"automation {automation_id} not found") - - if automation.status != AutomationStatus.ACTIVE: - raise DispatchError( - f"automation {automation_id} is {automation.status.value}, not active" - ) - - trigger = await _find_manual_trigger(session, automation_id) - if trigger is None: - raise DispatchError( - f"automation {automation_id} has no enabled manual trigger" - ) - - return await dispatch_run( - session=session, - automation=automation, - trigger=trigger, - runtime_inputs=runtime_inputs, - ) - - -async def _load_automation( - session: AsyncSession, automation_id: int -) -> Automation | None: - stmt = select(Automation).where(Automation.id == automation_id) - return (await session.execute(stmt)).scalar_one_or_none() - - -async def _find_manual_trigger( - session: AsyncSession, automation_id: int -) -> AutomationTrigger | None: - stmt = ( - select(AutomationTrigger) - .where( - AutomationTrigger.automation_id == automation_id, - AutomationTrigger.type == TriggerType.MANUAL, - AutomationTrigger.enabled.is_(True), - ) - .limit(1) - ) - return (await session.execute(stmt)).scalar_one_or_none() diff --git a/surfsense_backend/app/automations/triggers/manual/params.py b/surfsense_backend/app/automations/triggers/manual/params.py deleted file mode 100644 index 577655086..000000000 --- a/surfsense_backend/app/automations/triggers/manual/params.py +++ /dev/null @@ -1,9 +0,0 @@ -"""``ManualTriggerParams`` — params for the ``manual`` trigger (empty in v1).""" - -from __future__ import annotations - -from pydantic import BaseModel, ConfigDict - - -class ManualTriggerParams(BaseModel): - model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 64c8c6585..ef1c9312a 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -120,4 +120,4 @@ router.include_router(youtube_router) # YouTube playlist resolution router.include_router(prompts_router) router.include_router(memory_router) # User personal memory (memory.md style) router.include_router(team_memory_router) # Search-space team memory -router.include_router(automations_router) # Automations (manual run-now) +router.include_router(automations_router) # Automations CRUD + run history From 9d6e9b7e2d4edf97cd68e3f7fbbd535ba398c378 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Wed, 27 May 2026 14:58:10 -0700 Subject: [PATCH 064/133] feat: enhance task management and timeout configurations in multi-agent chat - Added new environment variables for controlling task execution limits, including `SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`, `SURFSENSE_TASK_BATCH_CONCURRENCY`, and `SURFSENSE_TASK_BATCH_MAX_SIZE`. - Updated documentation to reflect new batch processing capabilities for `task` calls, allowing for concurrent execution of multiple subagent tasks. - Improved error handling and receipt generation for deliverables, ensuring consistent feedback on task status. - Refactored middleware to incorporate search space ID for better task management. --- surfsense_backend/.env.example | 47 ++ .../system_prompt/prompts/routing.md | 70 ++ .../prompts/tools/task/description.md | 59 +- .../constants.py | 71 ++ .../middleware.py | 21 +- .../spawn_paused.py | 84 +++ .../task_tool.py | 674 +++++++++++++++++- .../shared/kb_context_projection.py | 4 +- .../shared/permissions/ask/request.py | 3 +- .../multi_agent_chat/middleware/stack.py | 1 + .../builtins/deliverables/system_prompt.md | 12 +- .../deliverables/tools/generate_image.py | 78 +- .../builtins/deliverables/tools/podcast.py | 111 ++- .../builtins/deliverables/tools/report.py | 100 ++- .../builtins/deliverables/tools/resume.py | 202 ++++-- .../deliverables/tools/video_presentation.py | 114 ++- .../knowledge_base/system_prompt_cloud.md | 9 +- .../knowledge_base/system_prompt_desktop.md | 9 +- .../builtins/memory/system_prompt.md | 8 +- .../builtins/research/system_prompt.md | 6 +- .../connectors/airtable/system_prompt.md | 8 +- .../connectors/calendar/system_prompt.md | 9 +- .../connectors/clickup/system_prompt.md | 8 +- .../connectors/confluence/system_prompt.md | 7 +- .../connectors/discord/system_prompt.md | 7 +- .../connectors/dropbox/system_prompt.md | 7 +- .../connectors/gmail/system_prompt.md | 9 +- .../connectors/gmail/tools/send_email.py | 148 ++-- .../connectors/google_drive/system_prompt.md | 7 +- .../connectors/jira/system_prompt.md | 8 +- .../connectors/linear/system_prompt.md | 8 +- .../connectors/luma/system_prompt.md | 7 +- .../connectors/notion/system_prompt.md | 7 +- .../connectors/notion/tools/delete_page.py | 140 +++- .../connectors/onedrive/system_prompt.md | 7 +- .../connectors/slack/system_prompt.md | 8 +- .../connectors/teams/system_prompt.md | 7 +- .../subagents/shared/md_file_reader.py | 13 + .../subagents/shared/snippets/__init__.py | 6 + .../shared/snippets/output_contract_base.md | 6 + .../shared/snippets/verifiable_handle.md | 10 + .../multi_agent_chat/subagents/shared/spec.py | 32 +- .../subagents/shared/subagent_builder.py | 56 +- .../app/agents/new_chat/filesystem_state.py | 31 + .../agents/new_chat/middleware/compaction.py | 3 +- .../agents/new_chat/middleware/doom_loop.py | 3 +- .../new_chat/middleware/kb_persistence.py | 76 ++ .../agents/new_chat/middleware/permission.py | 3 +- .../app/agents/new_chat/state_reducers.py | 34 + .../app/agents/new_chat/tools/podcast.py | 57 +- .../new_chat/tools/video_presentation.py | 58 +- .../app/agents/shared/__init__.py | 9 + .../app/agents/shared/deliverable_wait.py | 123 ++++ .../app/agents/shared/receipt.py | 161 +++++ .../app/agents/shared/receipt_command.py | 71 ++ .../app/etl_pipeline/etl_pipeline_service.py | 4 +- .../app/services/composio_service.py | 9 +- .../app/services/gmail/kb_sync_service.py | 4 +- .../google_calendar/kb_sync_service.py | 8 +- .../app/services/jira/kb_sync_service.py | 8 +- surfsense_backend/app/services/llm_service.py | 6 +- .../app/services/onedrive/kb_sync_service.py | 4 +- .../app/tasks/chat/stream_new_chat.py | 4 +- .../tasks/chat/streaming/handlers/tool_end.py | 37 +- .../generate_video_presentation/emission.py | 16 +- .../app/utils/document_converters.py | 4 +- 66 files changed, 2561 insertions(+), 380 deletions(-) create mode 100644 surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md create mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md create mode 100644 surfsense_backend/app/agents/shared/__init__.py create mode 100644 surfsense_backend/app/agents/shared/deliverable_wait.py create mode 100644 surfsense_backend/app/agents/shared/receipt.py create mode 100644 surfsense_backend/app/agents/shared/receipt_command.py diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index b05369412..70cf687d8 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -357,3 +357,50 @@ LANGSMITH_PROJECT=surfsense # updates and deletes — the TTL only bounds staleness for bulk-import # paths that bypass the ORM. Set to 0 to disable the cache. # SURFSENSE_CONNECTOR_DISCOVERY_TTL_SECONDS=30 + +# ----------------------------------------------------------------------------- +# `task` boundary controls (Hermes-inspired improvements) +# ----------------------------------------------------------------------------- +# Wall-clock budget for a single ``task(subagent, ...)`` invocation in +# seconds. Subagents that run hot (slow image vendors, sluggish embedders, +# wedged MCP servers) would otherwise pin the orchestrator until the next +# checkpoint heartbeat fires. On timeout the runtime cancels the underlying +# coroutine and synthesizes a ToolMessage telling the orchestrator to treat +# the result as ``status=error``. Set to 0 to disable the cap entirely. +# Default: 300.0 +# SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS=300 + +# Batch-mode (``task(tasks=[...])``) concurrency cap and max batch size. +# Concurrency is enforced via an ``asyncio.Semaphore`` so a runaway fanout +# cannot starve unrelated subagents (each child still owns an LLM call and +# its own DB session). Max-size is a hard safety net for prompt-injection / +# runaway loops; the orchestrator rarely needs more than a handful of +# concurrent specialists. Set concurrency to 1 to effectively serialise +# batches without changing the schema. +# SURFSENSE_TASK_BATCH_CONCURRENCY=3 +# SURFSENSE_TASK_BATCH_MAX_SIZE=8 + +# Soft per-turn cap on cumulative ``task(...)`` invocations across all +# subagents. Once the sum of ``state['billable_calls']`` crosses this +# number, the runtime appends a one-shot warning ToolMessage telling the +# orchestrator to wrap up rather than launching more specialists. Tunable +# so heavy-research turns (15+ legitimate specialist calls) don't trip the +# alarm in production. Set to 0 to disable the warning entirely. +# SURFSENSE_SUBAGENT_BILLABLE_THRESHOLD=15 + +# Per-workspace spawn-paused kill switch — set via Redis at runtime, not +# this env var. The env var below only disables the check itself (useful +# for local dev without Redis). To pause a workspace in production: +# redis-cli SET surfsense:spawn_paused: 1 EX 600 +# redis-cli DEL surfsense:spawn_paused: +# The check is fail-open: a Redis blip never blocks ``task(...)``. +# SURFSENSE_TASK_SPAWN_PAUSED_DISABLED=false + +# Note on Celery-backed deliverables (generate_podcast, +# generate_video_presentation): these tools poll the artefact row until +# it reaches a terminal status — they do NOT use an internal wall-clock +# budget. The effective ceiling is SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS +# (above, default 300s) in multi-agent mode and the chat's HTTP / process +# lifetime in single-agent mode. If your podcasts or videos routinely +# exceed 5 minutes, raise SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS (or +# set it to 0 to disable that ceiling entirely). diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md index 4e27381d3..1038dde3d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md @@ -33,6 +33,15 @@ Rules for `task`: - Neither's prompt references the other's output, and - They target different specialists, OR the same specialist with non-overlapping scopes (e.g. reading two unrelated paths). +- **Batch shape for many-shot fanout.** When a single user request expands + to **3 or more independent specialist calls** (e.g. "create five issues + from this list"), prefer the batch shape: + `task(tasks=[{description, subagent_type}, ...])`. The runtime fans them + out concurrently under a small semaphore and aggregates one ToolMessage + per child prefixed with `[task ]`. Batched children **do not + support human-in-the-loop interrupts** — if one needs approval it surfaces + an error and you re-dispatch it as a single (non-batched) `task(...)` call. + For 1–2 independent calls, just emit two separate `task(...)` calls. - **Serialise dependent work across turns.** If one specialist's output must inform another's input (e.g. "find the roadmap in my KB, then email it to Maya"), invoke them on consecutive turns — first finishes, @@ -93,4 +102,65 @@ user: "Find my Q2 roadmap doc in the KB and email a summary to Maya." task(gmail, "Send an email to Maya with subject 'Q2 roadmap summary' and the following body: .") + + +user: "Create issues in Linear for each of these five bugs: " +→ Many-shot independent fanout — use the batch shape: + task(tasks=[ + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + ]) + Read back the `[task 0]`…`[task 4]` blocks in the combined ToolMessage and + verify each via its Receipt's `verifiable_url` per the `` + teaching before confirming to the user. + + + +user: "Make a 30-second podcast of this conversation." +→ Celery-backed deliverable. The `deliverables` subagent dispatches the + Celery job and then **waits for it to finish** before returning. The + call may take 10-60 seconds (or longer for video presentations) — + that is intentional, not a hang. You always get back one of two + Receipt shapes: + task(deliverables, "Generate a podcast titled '' from the + following content. Use a 30-second style brief. Return the podcast + id and title.\n\n<source content>") + Outcomes: + - **`status="success"`**: the audio is saved. Tell the user the + podcast is **ready** and quote the `external_id` / `preview` so + they can find it in the podcast panel. + - **`status="failed"`**: surface the Receipt's `error` field + verbatim. Do NOT silently re-dispatch — the backend already tried + and reported a real error. + Same two-way pattern applies to video presentations (which take + longer to render, but still return a terminal status). If a + `task(deliverables, ...)` invocation itself times out at the subagent + layer (separate from the Receipt), that's an operator-side problem + with the subagent invoke timeout, not a deliverable failure — pass + the message through and stop. +</example> + +<example> +user: "Post the launch announcement to #general and let me know when it's up." +→ Mutating subagent + user wants external confirmation. Apply the + `<verification>` teaching: the slack subagent's reply is a self-report; + check its `evidence.receipts` for a Receipt with `status="success"` and + a `verifiable_url`, then fetch that URL to confirm before reporting back. + This turn: + task(slack, "Post '<launch announcement text>' to #general. + Return the message permalink.") + Next turn (with the receipt's `verifiable_url` in hand): + scrape_webpage(url=<verifiable_url from slack receipt>) + → confirm the post is live, then tell the user it's up with the URL. + If the slack reply has NO Receipt with `status="success"`, treat it as a + silent failure: surface the error verbatim, do not retry. +</example> </routing> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md index 2f47d4df1..d6a81d8d3 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md @@ -4,12 +4,69 @@ `<specialists>` for the live roster. - Each subagent runs in isolation with its own tool stack and context, and returns a single synthesized result. - - Args: + - Args (single mode): - `subagent_type` — name of the specialist to invoke (must match an entry in `<specialists>`). - `description` — the FULL task prompt. The specialist cannot see this thread, so include all context and constraints, plus what you need back. The specialist will respond in its own format — don't dictate one. + - Args (batch mode): + - `tasks` — array of `{description, subagent_type}` objects to fan out + concurrently. Mutually exclusive with single-mode args. Use when a + single request expands to **3 or more independent specialist calls** + (e.g. "create five issues from this list"). Children run under a + small concurrency cap and the runtime returns one ToolMessage block + per child, prefixed with `[task <index>]`. **Batched children do not + support human-in-the-loop interrupts** — if any child needs approval + it surfaces an error and you must re-dispatch that single task as a + non-batched `task(...)` call. - Routing rules (when to call, how often, how to scope) live in `<routing>`. + <verification> + A subagent's natural-language reply is a **self-report**, not proof. The + specialist might claim a Slack message was posted, a Jira issue was + created, or a report was generated even when the underlying tool call + failed silently or was rate-limited. Treat success language ("Done", + "Posted to #general", "Created ENG-42") as a hypothesis, not a fact. + + Two ground-truth signals are always available to verify a mutating + subagent's claim: + + 1. **`state['receipts']`** — every mutating tool emits a structured + `Receipt` (route, type, operation, status, external_id, + verifiable_url, preview) into this append-only list. The supervisor + never sees the raw list directly, but each subagent's + `<output_contract>` carries the matching Receipt(s) under + `evidence.receipts`. If a subagent reports success with NO matching + Receipt at `status="success"` (or `"pending"` for async deliverables + like podcasts/videos), the operation did not happen — treat as + failure and surface that to the user verbatim, do not retry blindly. + + 2. **`scrape_webpage`** — when a Receipt carries a `verifiable_url` + (Notion page URL, Slack permalink, Jira issue URL, Linear identifier + URL, etc.), you can fetch that URL and confirm the operation + externally. Use this for high-stakes mutations the user explicitly + called out (e.g. "send the launch email to the whole team") or when + the subagent's self-report contradicts what the user expected. + + **Receipt status semantics — read carefully:** + + - `status="success"`: the mutation already committed in the backend. + If a `verifiable_url` is present and the request was high-stakes, + you may `scrape_webpage` it to externally confirm. Otherwise trust + the Receipt and tell the user it is done. Celery-backed deliverables + (podcasts, video presentations) also land here — the subagent + already waited for the worker to finish, so a `success` Receipt + means the artefact really is saved. + - `status="failed"`: a Receipt with this status carries the backend's + error in its `error` field. Surface that text verbatim to the user; + re-routing or retrying is only appropriate when the user explicitly + asks for it. + - `status="pending"`: rare today — current mutating tools wait for + their backend before returning. If you ever do see a pending + Receipt, tell the user the work has been **kicked off** (quote the + `external_id` / `preview` so they can find it later), do not + `scrape_webpage` it, and do not re-dispatch the same + `task(...)` call hoping it will be done "this time". + </verification> diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py index 6c4519f3a..e11f3c3ec 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py @@ -2,6 +2,8 @@ from __future__ import annotations +import os + # Mirror of deepagents.middleware.subagents._EXCLUDED_STATE_KEYS. EXCLUDED_STATE_KEYS = frozenset( { @@ -16,3 +18,72 @@ EXCLUDED_STATE_KEYS = frozenset( # Match the parent graph's budget; the LangGraph default of 25 trips on # multi-step subagent runs. DEFAULT_SUBAGENT_RECURSION_LIMIT = 10_000 + + +def _read_timeout_env(name: str, default: float) -> float: + """Parse ``name`` from the environment; fall back to ``default`` on bad values. + + Kept as a free function so the module-level constants stay constants + after import; tests can monkeypatch this and re-evaluate via + ``importlib.reload`` if they need a different value mid-process. + """ + raw = os.environ.get(name) + if not raw: + return default + try: + value = float(raw) + except (TypeError, ValueError): + return default + return value if value > 0 else default + + +# Wall-clock budget for a single ``task(subagent, ...)`` invocation. +# Subagents that run hot (image generation with slow vendors, KB writes +# behind a sluggish embedder) can otherwise wedge the orchestrator until +# the next checkpoint heartbeat. ``0`` disables the timeout entirely. +DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS: float = _read_timeout_env( + "SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS", + default=300.0, +) + + +def _read_int_env(name: str, default: int) -> int: + raw = os.environ.get(name) + if not raw: + return default + try: + value = int(raw) + except (TypeError, ValueError): + return default + return value if value > 0 else default + + +# Maximum number of children that ``task(..., tasks=[...])`` runs in +# parallel via ``asyncio.gather`` + ``Semaphore``. Bounded so a runaway +# fanout cannot starve unrelated subagents (each child still owns an +# LLM call + DB session). Set ``SURFSENSE_TASK_BATCH_CONCURRENCY=1`` to +# effectively serialise batches without changing the schema. +DEFAULT_SUBAGENT_BATCH_CONCURRENCY: int = _read_int_env( + "SURFSENSE_TASK_BATCH_CONCURRENCY", + default=3, +) + +# Max number of children in a single batched ``task`` call. Hard upper +# bound is a safety net for prompt-injection / runaway loops; the orchestrator +# rarely needs more than a handful of concurrent specialists. +MAX_SUBAGENT_BATCH_SIZE: int = _read_int_env( + "SURFSENSE_TASK_BATCH_MAX_SIZE", + default=8, +) + + +# Soft threshold for per-turn cumulative ``task(...)`` invocations across +# **all** subagents. Once the sum of ``state['billable_calls']`` values +# crosses this number, the runtime appends a one-shot warning ToolMessage +# instructing the orchestrator to wrap up the turn. Tunable so heavy-research +# turns (which legitimately need 15+ specialist calls) don't trip the alarm +# in production. Set to ``0`` to disable the warning entirely. +DEFAULT_SUBAGENT_BILLABLE_THRESHOLD: int = _read_int_env( + "SURFSENSE_SUBAGENT_BILLABLE_THRESHOLD", + default=15, +) diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py index 0119752c1..6cc71f252 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py @@ -16,6 +16,9 @@ from langchain.agents import create_agent from langchain.chat_models import init_chat_model from langgraph.types import Checkpointer +from app.agents.multi_agent_chat.subagents.shared.spec import ( + SURF_CONTEXT_HINT_PROVIDER_KEY, +) from app.utils.perf import get_perf_logger from .task_tool import build_task_tool_with_parent_config @@ -34,6 +37,7 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): subagents: list[SubAgent | CompiledSubAgent], system_prompt: str | None = TASK_SYSTEM_PROMPT, task_description: str | None = None, + search_space_id: int | None = None, ) -> None: self._surf_checkpointer = checkpointer super(SubAgentMiddleware, self).__init__() @@ -43,8 +47,17 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): ) self._backend = backend self._subagents = subagents + # Search-space id is captured at build time (the orchestrator runs in + # exactly one search space for its lifetime). The spawn-paused kill + # switch keys on it so an operator can quarantine one workspace + # without affecting the rest of the deployment. + self._search_space_id = search_space_id subagent_specs = self._surf_compile_subagent_graphs() - task_tool = build_task_tool_with_parent_config(subagent_specs, task_description) + task_tool = build_task_tool_with_parent_config( + subagent_specs, + task_description, + search_space_id=search_space_id, + ) if system_prompt and subagent_specs: agents_desc = "\n".join( f"- {s['name']}: {s['description']}" for s in subagent_specs @@ -64,6 +77,10 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): for spec in self._subagents: spec_start = time.perf_counter() + # Provider may be ``None`` (no hint), in which case task_tool + # skips the prepend step. We forward the key unconditionally so + # the registry shape is uniform. + hint_provider = cast(dict, spec).get(SURF_CONTEXT_HINT_PROVIDER_KEY) if "runnable" in spec: compiled = cast(CompiledSubAgent, spec) specs.append( @@ -71,6 +88,7 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): "name": compiled["name"], "description": compiled["description"], "runnable": compiled["runnable"], + SURF_CONTEXT_HINT_PROVIDER_KEY: hint_provider, } ) timings.append( @@ -108,6 +126,7 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): "name": spec["name"], "description": spec["description"], "runnable": runnable, + SURF_CONTEXT_HINT_PROVIDER_KEY: hint_provider, } ) timings.append( diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py new file mode 100644 index 000000000..2c9e114e0 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py @@ -0,0 +1,84 @@ +"""Per-search-space spawn-paused kill switch for the ``task`` boundary. + +When operators see a runaway loop, a vendor outage, or a billing event +that requires immediate cessation of subagent traffic for a specific +workspace, they flip a Redis flag and the ``task`` tool short-circuits +without touching downstream services. The flag is **per-search-space** +so one tenant's incident never silences the rest of the deployment. + +Flag key: ``surfsense:spawn_paused:{search_space_id}`` +Flag value: any string-truthy value (we read presence, not contents). +TTL: set by whoever toggles the flag — this module never expires + keys on its own, since "the flag is on" is itself the signal + that a human (or alert) needs to investigate. + +The check is best-effort: Redis errors are logged but do not block the +``task`` invocation. Failing closed (block-on-redis-error) would let a +single Redis blip take the whole orchestrator offline; failing open +preserves availability and the alarm bells (rate-limits, cost spikes) +will surface the underlying outage. +""" + +from __future__ import annotations + +import contextlib +import logging +import os + +from app.config import config + +logger = logging.getLogger(__name__) + + +# Operators can disable the check entirely (e.g. local dev without Redis) +# by setting ``SURFSENSE_TASK_SPAWN_PAUSED_DISABLED=1``. Default is +# enabled so production never relies on flipping an opt-out flag. +_DISABLED = os.environ.get( + "SURFSENSE_TASK_SPAWN_PAUSED_DISABLED", "" +).strip().lower() in { + "1", + "true", + "yes", + "on", +} + + +def _flag_key(search_space_id: int) -> str: + return f"surfsense:spawn_paused:{search_space_id}" + + +async def is_spawn_paused(search_space_id: int | None) -> bool: + """Return ``True`` iff the workspace's spawn-paused flag is set in Redis. + + A ``None`` search-space (e.g. dev paths that did not plumb the id + through yet) bypasses the check. So does a Redis outage — see module + docstring for the fail-open rationale. + """ + if _DISABLED or search_space_id is None: + return False + try: + # Local import keeps the cold-path import cheap and lets routes + # that never call ``task`` skip the redis dependency entirely. + import redis.asyncio as aioredis # type: ignore[import-not-found] + + client = aioredis.from_url(config.REDIS_APP_URL, decode_responses=True) + try: + raw = await client.get(_flag_key(search_space_id)) + finally: + # ``aclose()`` is the async-safe variant on redis-py >=5; fall back + # to ``close()`` for older clients pinned in tests. + close = getattr(client, "aclose", None) or getattr(client, "close", None) + if callable(close): + with contextlib.suppress(Exception): + await close() # type: ignore[misc] + return bool(raw) + except Exception: + logger.warning( + "spawn_paused check failed for search_space_id=%s; failing open.", + search_space_id, + exc_info=True, + ) + return False + + +__all__ = ["is_spawn_paused"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py index c3babab83..91a0be506 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py @@ -8,9 +8,12 @@ re-raises any new pending interrupt back to the parent. from __future__ import annotations +import asyncio +import json import logging import time -from typing import Annotated, Any, NoReturn +from collections.abc import Awaitable +from typing import Annotated, Any, NoReturn, TypeVar from deepagents.middleware.subagents import TASK_TOOL_DESCRIPTION from langchain.tools import BaseTool, ToolRuntime @@ -20,6 +23,10 @@ from langchain_core.tools import StructuredTool from langgraph.errors import GraphInterrupt from langgraph.types import Command, Interrupt +from app.agents.multi_agent_chat.subagents.shared.spec import ( + SURF_CONTEXT_HINT_PROVIDER_KEY, + ContextHintProvider, +) from app.observability import metrics as ot_metrics, otel as ot from app.utils.perf import get_perf_logger @@ -29,7 +36,13 @@ from .config import ( has_surfsense_resume, subagent_invoke_config, ) -from .constants import EXCLUDED_STATE_KEYS +from .constants import ( + DEFAULT_SUBAGENT_BATCH_CONCURRENCY, + DEFAULT_SUBAGENT_BILLABLE_THRESHOLD, + DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS, + EXCLUDED_STATE_KEYS, + MAX_SUBAGENT_BATCH_SIZE, +) from .propagation import wrap_with_tool_call_id from .resume import ( build_resume_command, @@ -37,11 +50,70 @@ from .resume import ( get_first_pending_subagent_interrupt, hitlrequest_action_count, ) +from .spawn_paused import is_spawn_paused logger = logging.getLogger(__name__) _perf_log = get_perf_logger() +class SubagentInvokeTimeoutError(Exception): + """Raised when ``subagent.ainvoke`` exceeds the configured wall-clock budget. + + Carries the subagent name and the elapsed seconds so the caller can + synthesize a ToolMessage that the orchestrator can act on (re-route, + surface to the user, or retry with a smaller scope). + """ + + def __init__(self, subagent_type: str, elapsed_seconds: float) -> None: + super().__init__( + f"subagent {subagent_type!r} exceeded " + f"{DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS:.0f}s budget " + f"(elapsed={elapsed_seconds:.1f}s)" + ) + self.subagent_type = subagent_type + self.elapsed_seconds = elapsed_seconds + + +_T = TypeVar("_T") + + +async def _ainvoke_with_timeout[T]( + coro: Awaitable[_T], *, subagent_type: str, started_at: float +) -> _T: + """Apply :data:`DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS` to ``coro``. + + A non-positive timeout disables the cap (configurable via the + ``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` env var). On expiry the + underlying task is cancelled and :class:`SubagentInvokeTimeoutError` is + raised — the caller wraps it into a synthetic ToolMessage so the + orchestrator can decide what to do. + """ + timeout = DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS + if timeout <= 0: + return await coro + try: + return await asyncio.wait_for(coro, timeout=timeout) + except TimeoutError as exc: + elapsed = time.perf_counter() - started_at + raise SubagentInvokeTimeoutError(subagent_type, elapsed) from exc + + +def _synthesize_timeout_command( + exc: SubagentInvokeTimeoutError, *, tool_call_id: str +) -> Command: + """Turn a :class:`SubagentInvokeTimeoutError` into a ToolMessage the parent can read.""" + content = ( + f"Subagent {exc.subagent_type!r} timed out after " + f"{exc.elapsed_seconds:.1f}s (budget=" + f"{DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS:.0f}s). " + "The work was cancelled. Treat as status=error; re-route with a " + "narrower scope or different specialist." + ) + return Command( + update={"messages": [ToolMessage(content=content, tool_call_id=tool_call_id)]} + ) + + def _reraise_stamped_subagent_interrupt( gi: GraphInterrupt, tool_call_id: str ) -> NoReturn: @@ -70,11 +142,24 @@ def _reraise_stamped_subagent_interrupt( def build_task_tool_with_parent_config( subagents: list[dict[str, Any]], task_description: str | None = None, + *, + search_space_id: int | None = None, ) -> BaseTool: """Upstream ``_build_task_tool`` + parent ``runtime.config`` propagation + resume bridging.""" subagent_graphs: dict[str, Runnable] = { spec["name"]: spec["runnable"] for spec in subagents } + # Per-subagent context-hint providers (see ``SurfSenseSubagentSpec``). + # The mapping is sparse: only routes that opted in via ``pack_subagent`` + # appear here, and the value is invoked once per ``task(...)`` call to + # generate a short string prepended to the subagent's first + # ``HumanMessage``. Failures are logged and swallowed — a broken hint + # provider must never prevent the underlying task from running. + subagent_hint_providers: dict[str, ContextHintProvider] = { + spec["name"]: provider + for spec in subagents + if (provider := spec.get(SURF_CONTEXT_HINT_PROVIDER_KEY)) is not None + } subagent_description_str = "\n".join( f"- {s['name']}: {s['description']}" for s in subagents ) @@ -88,6 +173,120 @@ def build_task_tool_with_parent_config( else: description = task_description + def _billable_call_update( + subagent_type: str, runtime: ToolRuntime + ) -> dict[str, Any]: + """Build the per-call ``billable_calls`` delta + an optional warning. + + The orchestrator's ``billable_calls`` map is summed by + :func:`_int_counter_merge_reducer`, so we always emit + ``{subagent_type: 1}`` and let the reducer accumulate. If the + cumulative count *after* this call would cross the configured + threshold, we also slip a soft ``messages`` entry into the update + so the orchestrator can read it on its next step and self-limit. + Returning a plain ``dict`` (vs. an extra :class:`Command`) keeps + the helper composable with the existing single/batch return paths. + """ + delta: dict[str, Any] = {"billable_calls": {subagent_type: 1}} + threshold = DEFAULT_SUBAGENT_BILLABLE_THRESHOLD + if threshold <= 0: + return delta + prior = runtime.state.get("billable_calls") or {} + # ``prior`` may be a plain dict or a reducer-managed mapping; only + # int values are counted so a malformed checkpoint can't crash us. + prior_total = sum(v for v in prior.values() if isinstance(v, int)) + new_total = prior_total + 1 + if prior_total < threshold <= new_total: + warn = ( + f"[budget warning] This turn has dispatched {new_total} " + f"subagent calls (soft cap = {threshold}). Wrap up the " + "user's request with what you have rather than launching " + "more specialists; surface a partial answer if needed." + ) + delta["_billable_warn_text"] = warn + return delta + + def _attach_billable( + cmd: Command, subagent_type: str, runtime: ToolRuntime + ) -> Command: + """Merge the per-call billable counter (and warning) into ``cmd``.""" + delta = _billable_call_update(subagent_type, runtime) + warn_text = delta.pop("_billable_warn_text", None) + # ``cmd.update`` may be a dict or LangGraph ``UpdateDict``; defensively + # copy so we don't mutate state shared across other tool returns. + update = dict(getattr(cmd, "update", {}) or {}) + for key, value in delta.items(): + update[key] = value + if warn_text: + existing_msgs = list(update.get("messages") or []) + existing_msgs.append( + ToolMessage(content=warn_text, tool_call_id=runtime.tool_call_id) + ) + update["messages"] = existing_msgs + return Command(update=update) + + def _safe_message_text(msg: Any) -> str: + """Pull text out of a BaseMessage without trusting the ``.text`` property. + + ``BaseMessage.text`` walks ``content_blocks`` and crashes with + ``TypeError: 'NoneType' object is not iterable`` when ``content`` is + ``None`` (common for tool-call AIMessages whose payload is purely + structured). ``getattr(msg, "text", None)`` does not catch this + because Python evaluates the property body before falling back to + the default. Read ``content`` directly and coerce defensively. + """ + try: + content = getattr(msg, "content", None) + except Exception: + content = None + if content is None: + return "" + if isinstance(content, str): + return content + if isinstance(content, list): + parts: list[str] = [] + for block in content: + if isinstance(block, str): + parts.append(block) + elif isinstance(block, dict): + block_text = block.get("text") or block.get("content") + if isinstance(block_text, str): + parts.append(block_text) + return " ".join(parts) + return str(content) + + def _build_tool_trace(messages: list[Any]) -> list[dict[str, Any]]: + """Compress the subagent's message stream into a compact tool trace. + + Each entry is ``{"tool": <name>, "status": "ok"|"error", "preview": + <≤120 chars>}`` so the orchestrator can show "this is what your + specialist actually did" without dumping the full message stream + back through the prompt. The list is attached to the returned + ToolMessage's ``additional_kwargs`` (under ``"surf_tool_trace"``); + the LLM never sees it, but UI / observability code can pluck it + out of the checkpoint. + """ + trace: list[dict[str, Any]] = [] + for msg in messages: + tool_name = getattr(msg, "name", None) + tool_call_id_attr = getattr(msg, "tool_call_id", None) + if not tool_name and not tool_call_id_attr: + # Only ToolMessages have either field; skip AIMessage / + # HumanMessage / SystemMessage frames. + continue + status = getattr(msg, "status", None) or "ok" + preview = _safe_message_text(msg).strip().replace("\n", " ") + if len(preview) > 120: + preview = preview[:117] + "..." + trace.append( + { + "tool": tool_name or "<unknown>", + "status": status, + "preview": preview, + } + ) + return trace + def _return_command_with_state_update(result: dict, tool_call_id: str) -> Command: if "messages" not in result: msg = ( @@ -106,15 +305,51 @@ def build_task_tool_with_parent_config( "output to forward back to the user." ) raise ValueError(msg) - last_text = getattr(messages[-1], "text", None) or "" - message_text = last_text.rstrip() + message_text = _safe_message_text(messages[-1]).rstrip() + # Tool-trace is purely observability — wrap defensively so a single + # malformed frame never bubbles up and kills the whole user turn. + try: + tool_trace = _build_tool_trace(messages) + except Exception: + logger.exception( + "Failed to build tool_trace for subagent return; " + "continuing without trace." + ) + tool_trace = [] + tool_msg = ToolMessage(message_text, tool_call_id=tool_call_id) + if tool_trace: + # ``additional_kwargs`` is a free-form dict on BaseMessage; using + # a ``surf_`` prefix avoids collision with provider-specific keys + # (e.g. Anthropic's ``cache_control``). The LLM doesn't see it; + # consumers (UI, observability) read it off the checkpoint. + tool_msg.additional_kwargs["surf_tool_trace"] = tool_trace return Command( update={ **state_update, - "messages": [ToolMessage(message_text, tool_call_id=tool_call_id)], + "messages": [tool_msg], } ) + def _resolve_context_hint( + subagent_type: str, description: str, runtime: ToolRuntime + ) -> str | None: + """Run the per-subagent hint provider; swallow & log any exception.""" + provider = subagent_hint_providers.get(subagent_type) + if provider is None: + return None + try: + hint = provider(runtime.state, description) + except Exception: + logger.exception( + "Context-hint provider for subagent %r raised; skipping hint.", + subagent_type, + ) + return None + if not hint or not isinstance(hint, str): + return None + cleaned = hint.strip() + return cleaned or None + def _validate_and_prepare_state( subagent_type: str, description: str, runtime: ToolRuntime ) -> tuple[Runnable, dict]: @@ -122,20 +357,308 @@ def build_task_tool_with_parent_config( subagent_state = { k: v for k, v in runtime.state.items() if k not in EXCLUDED_STATE_KEYS } - subagent_state["messages"] = [HumanMessage(content=description)] + hint = _resolve_context_hint(subagent_type, description, runtime) + if hint: + # Prepend as a tagged block so the subagent prompt can pattern-match + # on the section (and a future change can lift it into its own + # ``SystemMessage`` if needed). + payload = f"<context_hint>\n{hint}\n</context_hint>\n\n{description}" + else: + payload = description + subagent_state["messages"] = [HumanMessage(content=payload)] return subagent, subagent_state + def _merge_batch_results( + results: list[tuple[int, str, dict | str, dict | None]], + runtime: ToolRuntime, + ) -> Command: + """Combine per-child results into one Command with a combined ToolMessage. + + ``results`` is a list of ``(task_index, subagent_type, + payload_or_error_text, child_state_update)`` tuples — preserving the + input order so the orchestrator can map each block back to the task + it dispatched. State updates are merged by reducer for keys outside + :data:`EXCLUDED_STATE_KEYS`; everything else (``messages``, ``todos``, + etc.) is replaced by the synthesized aggregate ToolMessage. Every + child also contributes a ``billable_calls`` increment so cost + accounting matches single-mode dispatch. + """ + results.sort(key=lambda r: r[0]) + merged_state: dict[str, Any] = {} + billable_delta: dict[str, int] = {} + message_blocks: list[str] = [] + batch_trace: list[dict[str, Any]] = [] + for task_index, subagent_type, payload, state_update in results: + billable_delta[subagent_type] = billable_delta.get(subagent_type, 0) + 1 + if isinstance(payload, str): + # Pre-flight error or per-task exception text. + message_blocks.append(f"[task {task_index}] {payload}") + batch_trace.append( + { + "task_index": task_index, + "subagent_type": subagent_type, + "status": "error", + "tool_trace": [], + } + ) + continue + messages = payload.get("messages") or [] + last_text = _safe_message_text(messages[-1]).rstrip() if messages else "" + message_blocks.append( + f"[task {task_index}] {last_text or '<empty>'}" + ) + try: + child_trace = _build_tool_trace(messages) + except Exception: + logger.exception( + "Failed to build tool_trace for batch task_index=%d; continuing.", + task_index, + ) + child_trace = [] + batch_trace.append( + { + "task_index": task_index, + "subagent_type": subagent_type, + "status": "ok", + "tool_trace": child_trace, + } + ) + if state_update: + # Naive merge: later tasks win on scalar collisions; reducer-backed + # fields (``receipts``, ``files`` etc.) accumulate at apply time. + merged_state.update(state_update) + aggregate = "\n\n".join(message_blocks) + aggregate_msg = ToolMessage( + content=aggregate, tool_call_id=runtime.tool_call_id + ) + if batch_trace: + aggregate_msg.additional_kwargs["surf_tool_trace"] = batch_trace + update: dict[str, Any] = { + **merged_state, + "billable_calls": billable_delta, + "messages": [aggregate_msg], + } + # Soft-cap warning: check the cumulative count after attribution. + threshold = DEFAULT_SUBAGENT_BILLABLE_THRESHOLD + if threshold > 0: + prior = runtime.state.get("billable_calls") or {} + prior_total = sum(v for v in prior.values() if isinstance(v, int)) + new_total = prior_total + sum(billable_delta.values()) + if prior_total < threshold <= new_total: + update["messages"].append( + ToolMessage( + content=( + f"[budget warning] This turn has dispatched " + f"{new_total} subagent calls (soft cap = " + f"{threshold}). Wrap up the user's request with " + "what you have rather than launching more " + "specialists; surface a partial answer if needed." + ), + tool_call_id=runtime.tool_call_id, + ) + ) + return Command(update=update) + + async def _ainvoke_one_batch_child( + *, + task_index: int, + subagent_type: str, + description: str, + runtime: ToolRuntime, + semaphore: asyncio.Semaphore, + ) -> tuple[int, str, dict | str, dict | None]: + """Run one child of a batched ``task`` call under the concurrency cap. + + Errors are returned as plain text in slot 2 so a single child's + failure does not abort the whole batch. ``GraphInterrupt`` from a + batched child is currently treated as a hard failure for that child + only — batched HITL is intentionally out of scope for the v1 + rollout (see plan tier 2 item 4 risks). + """ + async with semaphore: + if subagent_type not in subagent_graphs: + allowed_types = ", ".join([f"`{k}`" for k in subagent_graphs]) + return ( + task_index, + subagent_type, + ( + f"Subagent {subagent_type!r} does not exist; " + f"allowed: {allowed_types}" + ), + None, + ) + subagent, subagent_state = _validate_and_prepare_state( + subagent_type, description, runtime + ) + sub_config = subagent_invoke_config(runtime) + started_at = time.perf_counter() + try: + result = await _ainvoke_with_timeout( + subagent.ainvoke(subagent_state, config=sub_config), + subagent_type=subagent_type, + started_at=started_at, + ) + except SubagentInvokeTimeoutError as exc: + logger.warning( + "Batch child %d (%s) timed out after %.1fs", + task_index, + subagent_type, + exc.elapsed_seconds, + ) + return (task_index, subagent_type, str(exc), None) + except GraphInterrupt: + # Batched HITL is unsupported in v1 — surface as a failure + # for this child so the rest of the batch still completes. + logger.warning( + "Batch child %d (%s) raised GraphInterrupt; batched HITL " + "is not supported. Re-dispatch this task as a single " + "(non-batched) `task(...)` call to get the HITL prompt.", + task_index, + subagent_type, + ) + return ( + task_index, + subagent_type, + ( + f"Subagent {subagent_type!r} needs human approval. " + "Re-dispatch this task as a single (non-batched) " + "`task(...)` call so the approval card can be shown." + ), + None, + ) + except Exception as exc: + logger.exception( + "Batch child %d (%s) raised: %s", + task_index, + subagent_type, + exc, + ) + return ( + task_index, + subagent_type, + f"Subagent {subagent_type!r} error: {exc}", + None, + ) + child_state_update = { + k: v for k, v in result.items() if k not in EXCLUDED_STATE_KEYS + } + return (task_index, subagent_type, result, child_state_update) + + def _coerce_batch_arg(tasks: Any) -> list[dict] | str: + """Rescue common LLM-side malformations of the ``tasks`` argument. + + Some providers serialise an array argument as a JSON-encoded string, + and small models occasionally hand back a single ``{description, + subagent_type}`` dict instead of a one-element array. Both are + recovered here with a WARN log so the issue is visible in metrics + but the user's turn still completes; truly broken shapes return a + plain string that the caller surfaces as the tool error. + """ + if isinstance(tasks, list): + return tasks + if isinstance(tasks, dict): + logger.warning( + "task: `tasks` was a single dict; coercing to a 1-element list. " + "Orchestrators should send `tasks=[{...}]` directly." + ) + return [tasks] + if isinstance(tasks, str): + stripped = tasks.strip() + if not stripped: + return "tasks: argument is empty." + try: + parsed = json.loads(stripped) + except json.JSONDecodeError as exc: + return ( + f"tasks: argument is a string but not valid JSON ({exc.msg}). " + "Send a JSON array of `{description, subagent_type}` objects." + ) + logger.warning( + "task: `tasks` was a JSON-encoded string; parsed to %s. " + "Orchestrators should send a JSON array directly.", + type(parsed).__name__, + ) + return _coerce_batch_arg(parsed) + return ( + f"tasks: unsupported type {type(tasks).__name__}; expected an array " + "of `{description, subagent_type}` objects." + ) + + async def _adispatch_batch( + tasks: list[dict], runtime: ToolRuntime + ) -> Command | str: + """Fan-out helper for the ``tasks`` array shape. + + Bounded by :data:`MAX_SUBAGENT_BATCH_SIZE` and concurrency-capped + at :data:`DEFAULT_SUBAGENT_BATCH_CONCURRENCY`. Returns a single + :class:`Command` that the LLM sees as one ToolMessage per child, + prefixed with ``[task <index>]`` so it can map back to the input + order. + """ + if not tasks: + return "tasks: array is empty; nothing to dispatch." + if len(tasks) > MAX_SUBAGENT_BATCH_SIZE: + return ( + f"tasks: too many children ({len(tasks)}); " + f"max is {MAX_SUBAGENT_BATCH_SIZE}. Split the batch." + ) + normalized: list[tuple[int, str, str]] = [] + for idx, item in enumerate(tasks): + if not isinstance(item, dict): + return ( + f"tasks[{idx}]: must be an object with description+subagent_type." + ) + description = item.get("description") + subagent_type = item.get("subagent_type") + if not isinstance(description, str) or not description.strip(): + return f"tasks[{idx}]: missing or empty 'description'." + if not isinstance(subagent_type, str) or not subagent_type.strip(): + return f"tasks[{idx}]: missing or empty 'subagent_type'." + normalized.append((idx, subagent_type.strip(), description)) + semaphore = asyncio.Semaphore(DEFAULT_SUBAGENT_BATCH_CONCURRENCY) + coros = [ + _ainvoke_one_batch_child( + task_index=idx, + subagent_type=subagent_type, + description=description, + runtime=runtime, + semaphore=semaphore, + ) + for idx, subagent_type, description in normalized + ] + results = await asyncio.gather(*coros) + return _merge_batch_results(list(results), runtime) + def task( description: Annotated[ - str, - "A detailed description of the task for the subagent to perform autonomously. Include all necessary context and specify the expected output format.", - ], + str | None, + "Single-mode: a detailed task description for the subagent. Required unless `tasks` is provided.", + ] = None, subagent_type: Annotated[ - str, - "The type of subagent to use. Must be one of the available agent types listed in the tool description.", - ], - runtime: ToolRuntime, + str | None, + "Single-mode: the type of subagent to use. Required unless `tasks` is provided.", + ] = None, + runtime: ToolRuntime = None, # type: ignore[assignment] + tasks: Annotated[ + list[dict] | None, + ( + "Batch-mode: array of `{description, subagent_type}` objects. " + "Synchronous path does not support batch mode; orchestrators " + "must use the async event loop to fan out." + ), + ] = None, ) -> str | Command: + if tasks is not None: + return ( + "task: batch mode (`tasks=[...]`) is only supported on the async " + "path. SurfSense orchestrators always run in an event loop, so " + "this should never fire — file a bug if you see it." + ) + if not description or not subagent_type: + return ( + "task: must provide either single-mode (`description`+`subagent_type`) " + "or batch-mode (`tasks`)." + ) if subagent_type not in subagent_graphs: allowed_types = ", ".join([f"`{k}`" for k in subagent_graphs]) return ( @@ -284,16 +807,65 @@ def build_task_tool_with_parent_config( async def atask( description: Annotated[ - str, - "A detailed description of the task for the subagent to perform autonomously. Include all necessary context and specify the expected output format.", - ], + str | None, + "Single-mode: a detailed task description for the subagent. Required unless `tasks` is provided.", + ] = None, subagent_type: Annotated[ - str, - "The type of subagent to use. Must be one of the available agent types listed in the tool description.", - ], - runtime: ToolRuntime, + str | None, + "Single-mode: the type of subagent to use. Required unless `tasks` is provided.", + ] = None, + runtime: ToolRuntime = None, # type: ignore[assignment] + tasks: Annotated[ + list[dict] | None, + ( + "Batch-mode: array of `{description, subagent_type}` objects " + "to fan out concurrently (max " + f"{MAX_SUBAGENT_BATCH_SIZE}, concurrency " + f"{DEFAULT_SUBAGENT_BATCH_CONCURRENCY}). Mutually exclusive " + "with single-mode args. Batched children do not support " + "human-in-the-loop interrupts; re-dispatch as single mode " + "if a child needs approval." + ), + ] = None, ) -> str | Command: atask_start = time.perf_counter() + # Kill switch: when ops flips the spawn-paused flag for this + # workspace, every ``task(...)`` invocation (single- or batch-mode) + # short-circuits with a clear ToolMessage so the orchestrator can + # tell the user what happened and stop hammering downstream APIs. + if await is_spawn_paused(search_space_id): + logger.warning( + "[hitl_route] atask SPAWN_PAUSED: search_space_id=%s tool_call_id=%s", + search_space_id, + runtime.tool_call_id, + ) + return ( + "task: subagent dispatch is currently paused for this workspace. " + "Acknowledge to the user that delegation is temporarily disabled " + "(ops kill switch); do not retry until the pause is lifted." + ) + if tasks is not None: + if description or subagent_type: + return ( + "task: cannot combine `tasks` with `description`/`subagent_type`. " + "Use either single-mode (description+subagent_type) or batch-mode (tasks)." + ) + if not runtime.tool_call_id: + raise ValueError("Tool call ID is required for subagent invocation") + coerced = _coerce_batch_arg(tasks) + if isinstance(coerced, str): + return coerced + logger.info( + "[hitl_route] atask BATCH ENTRY: size=%d tool_call_id=%s", + len(coerced), + runtime.tool_call_id, + ) + return await _adispatch_batch(coerced, runtime) + if not description or not subagent_type: + return ( + "task: must provide either single-mode (`description`+`subagent_type`) " + "or batch-mode (`tasks`)." + ) logger.info( "[hitl_route] atask ENTRY: subagent_type=%r tool_call_id=%s", subagent_type, @@ -358,11 +930,37 @@ def build_task_tool_with_parent_config( subagent_type=subagent_type, path=invoke_path ) as sp: try: - result = await subagent.ainvoke( - build_resume_command(resume_value, pending_id), - config=sub_config, + result = await _ainvoke_with_timeout( + subagent.ainvoke( + build_resume_command(resume_value, pending_id), + config=sub_config, + ), + subagent_type=subagent_type, + started_at=ainvoke_start, ) sp.set_attribute("subagent.outcome", ainvoke_outcome) + except SubagentInvokeTimeoutError as exc: + ainvoke_outcome = "timeout" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + logger.warning( + "Subagent %r ainvoke (resume) timed out after %.1fs", + subagent_type, + exc.elapsed_seconds, + ) + return _synthesize_timeout_command( + exc, tool_call_id=runtime.tool_call_id + ) except GraphInterrupt as gi: ainvoke_outcome = "interrupted" sp.set_attribute("subagent.outcome", ainvoke_outcome) @@ -408,10 +1006,34 @@ def build_task_tool_with_parent_config( subagent_type=subagent_type, path=invoke_path ) as sp: try: - result = await subagent.ainvoke( - subagent_state, config=sub_config + result = await _ainvoke_with_timeout( + subagent.ainvoke(subagent_state, config=sub_config), + subagent_type=subagent_type, + started_at=ainvoke_start, ) sp.set_attribute("subagent.outcome", ainvoke_outcome) + except SubagentInvokeTimeoutError as exc: + ainvoke_outcome = "timeout" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + logger.warning( + "Subagent %r ainvoke (fresh) timed out after %.1fs", + subagent_type, + exc.elapsed_seconds, + ) + return _synthesize_timeout_command( + exc, tool_call_id=runtime.tool_call_id + ) except GraphInterrupt as gi: ainvoke_outcome = "interrupted" sp.set_attribute("subagent.outcome", ainvoke_outcome) @@ -481,7 +1103,7 @@ def build_task_tool_with_parent_config( path=invoke_path, outcome=ainvoke_outcome, ) - return cmd + return _attach_billable(cmd, subagent_type, runtime) return StructuredTool.from_function( name="task", diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py index e8a4c9899..2685d8a9b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py @@ -52,9 +52,7 @@ class KbContextProjectionMiddleware(AgentMiddleware): # type: ignore[type-arg] messages.insert(insert_at, SystemMessage(content=tree_text)) priority_count = 0 if priority: - priority_count = ( - len(priority) if hasattr(priority, "__len__") else 1 - ) + priority_count = len(priority) if hasattr(priority, "__len__") else 1 messages.insert(insert_at, _render_priority_message(priority)) _perf_log.info( "[kb_context_projection] tree_chars=%d priority_items=%d elapsed=%.3fs", diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py index a725bfee1..3db51883d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py @@ -17,8 +17,7 @@ from langchain_core.tools import BaseTool from langgraph.types import interrupt from app.agents.new_chat.permissions import Rule -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot from .decision import normalize_permission_decision from .payload import PERMISSION_ASK_INTERRUPT_TYPE, build_permission_ask_payload diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py index c1ebe31ca..3b20d8915 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py @@ -173,6 +173,7 @@ def build_main_agent_deepagent_middleware( subagents=subagents, system_prompt=None, task_description=TASK_TOOL_DESCRIPTION, + search_space_id=search_space_id, ), resilience.model_call_limit, resilience.tool_call_limit, diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md index c44f131bb..413791037 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md @@ -42,14 +42,16 @@ Return **only** one JSON object (no markdown/prose): "evidence": { "artifact_type": "report" | "podcast" | "video_presentation" | "resume" | "image" | null, "artifact_id": string | null, - "artifact_location": string | null + "artifact_location": string | null, + "receipts": Receipt[] | null }, "next_step": string | null, "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` -> `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` -> `next_step` must be non-null. -- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +Route-specific rules: +- `evidence.receipts` quotes the Receipt(s) returned by `generate_report` / `generate_podcast` / `generate_video_presentation` / `generate_resume` / `generate_image` this turn, verbatim. The Receipt's `type` enum is one of `report` | `podcast` | `video_presentation` | `resume` | `image`. +<include snippet="output_contract_base"/> </output_contract> + +<include snippet="verifiable_handle"/> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py index ab9dbc0ea..f170a35db 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py @@ -4,11 +4,15 @@ import hashlib import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from litellm import aimage_generation from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.config import config from app.db import ( ImageGeneration, @@ -66,8 +70,9 @@ def create_generate_image_tool( @tool async def generate_image( prompt: str, + runtime: ToolRuntime, n: int = 1, - ) -> dict[str, Any]: + ) -> Command: """ Generate an image from a text description using AI image models. @@ -82,6 +87,21 @@ def create_generate_image_tool( Returns: A dictionary containing the generated image(s) for display in the chat. """ + + def _failed(payload: dict[str, Any], *, error: str) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="image", + operation="generate", + status="failed", + preview=prompt[:200] if prompt else None, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + try: # Use a per-call session so concurrent tool calls don't share an # AsyncSession (which is not concurrency-safe). The streaming @@ -93,7 +113,10 @@ def create_generate_image_tool( ) search_space = result.scalars().first() if not search_space: - return {"error": "Search space not found"} + return _failed( + {"error": "Search space not found"}, + error="Search space not found", + ) config_id = ( search_space.image_generation_config_id or IMAGE_GEN_AUTO_MODE_ID @@ -112,19 +135,19 @@ def create_generate_image_tool( # Call litellm based on config type if is_image_gen_auto_mode(config_id): if not ImageGenRouterService.is_initialized(): - return { - "error": "No image generation models configured. " + err = ( + "No image generation models configured. " "Please add an image model in Settings > Image Models." - } + ) + return _failed({"error": err}, error=err) response = await ImageGenRouterService.aimage_generation( prompt=prompt, model="auto", **gen_kwargs ) elif config_id < 0: cfg = _get_global_image_gen_config(config_id) if not cfg: - return { - "error": f"Image generation config {config_id} not found" - } + err = f"Image generation config {config_id} not found" + return _failed({"error": err}, error=err) model_string = _build_model_string( cfg.get("provider", ""), @@ -151,9 +174,8 @@ def create_generate_image_tool( ) db_cfg = cfg_result.scalars().first() if not db_cfg: - return { - "error": f"Image generation config {config_id} not found" - } + err = f"Image generation config {config_id} not found" + return _failed({"error": err}, error=err) model_string = _build_model_string( db_cfg.provider.value, @@ -200,7 +222,10 @@ def create_generate_image_tool( # Extract image URLs from response images = response_dict.get("data", []) if not images: - return {"error": "No images were generated"} + return _failed( + {"error": "No images were generated"}, + error="No images were generated", + ) first_image = images[0] revised_prompt = first_image.get("revised_prompt", prompt) @@ -219,11 +244,14 @@ def create_generate_image_tool( f"{db_image_gen_id}/image?token={access_token}" ) else: - return {"error": "No displayable image data in the response"} + return _failed( + {"error": "No displayable image data in the response"}, + error="No displayable image data in the response", + ) image_id = f"image-{hashlib.md5(image_url.encode()).hexdigest()[:12]}" - return { + payload = { "id": image_id, "assetId": image_url, "src": image_url, @@ -236,12 +264,26 @@ def create_generate_image_tool( "prompt": prompt, "image_count": len(images), } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="image", + operation="generate", + status="success", + external_id=str(db_image_gen_id), + verifiable_url=image_url, + preview=(revised_prompt or prompt)[:200], + ), + tool_call_id=runtime.tool_call_id, + ) except Exception as e: logger.exception("Image generation failed in tool") - return { - "error": f"Image generation failed: {e!s}", - "prompt": prompt, - } + err = f"Image generation failed: {e!s}" + return _failed( + {"error": err, "prompt": prompt}, + error=err, + ) return generate_image diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py index 55d9b3565..84617d38b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py @@ -1,12 +1,28 @@ -"""Factory for a podcast-generation tool that queues background work and returns an ID for polling.""" +"""Factory for a podcast-generation tool. +Dispatches the heavy generation to Celery and then polls the podcast row +until it reaches a terminal status (READY/FAILED). The tool always +returns a real terminal ``Receipt`` — never a pending one. The wait is +bounded by the existing per-invocation safety net +(``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` in multi-agent mode, +HTTP / process lifetime in single-agent mode). +""" + +import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import Podcast, PodcastStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_podcast_tool( search_space_id: int, @@ -19,9 +35,10 @@ def create_generate_podcast_tool( @tool async def generate_podcast( source_content: str, + runtime: ToolRuntime, podcast_title: str = "SurfSense Podcast", user_prompt: str | None = None, - ) -> dict[str, Any]: + ) -> Command: """ Generate a podcast from the provided content. @@ -70,23 +87,101 @@ def create_generate_podcast_tool( user_prompt=user_prompt, ) - print(f"[generate_podcast] Created podcast {podcast_id}, task: {task.id}") + logger.info( + "[generate_podcast] Created podcast %s, task: %s", + podcast_id, + task.id, + ) - return { - "status": PodcastStatus.PENDING.value, + # Wait until the Celery worker flips the row to a terminal + # state. The wait is bounded only by the subagent invoke + # timeout (multi-agent) or HTTP lifetime (single-agent) — + # see app.agents.shared.deliverable_wait for details. + terminal_status, columns, elapsed = await wait_for_deliverable( + model=Podcast, + row_id=podcast_id, + columns=[Podcast.status, Podcast.file_location], + terminal_statuses={PodcastStatus.READY, PodcastStatus.FAILED}, + ) + + if terminal_status == PodcastStatus.READY: + file_location = columns[1] if columns else None + logger.info( + "[generate_podcast] Podcast %s READY in %.2fs (file=%s)", + podcast_id, + elapsed, + file_location, + ) + payload: dict[str, Any] = { + "status": PodcastStatus.READY.value, + "podcast_id": podcast_id, + "title": podcast_title, + "file_location": file_location, + "message": ( + "Podcast generated and saved to your podcast panel." + ), + } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="podcast", + operation="generate", + status="success", + external_id=str(podcast_id), + preview=podcast_title, + ), + tool_call_id=runtime.tool_call_id, + ) + + # Only other terminal state is FAILED. + logger.warning( + "[generate_podcast] Podcast %s FAILED in %.2fs", + podcast_id, + elapsed, + ) + err = "Background worker reported FAILED status for this podcast." + payload = { + "status": PodcastStatus.FAILED.value, "podcast_id": podcast_id, "title": podcast_title, - "message": "Podcast generation started. This may take a few minutes.", + "error": err, } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="podcast", + operation="generate", + status="failed", + external_id=str(podcast_id), + preview=podcast_title, + error=err, + ), + tool_call_id=runtime.tool_call_id, + ) except Exception as e: error_message = str(e) - print(f"[generate_podcast] Error: {error_message}") - return { + logger.exception("[generate_podcast] Error: %s", error_message) + payload = { "status": PodcastStatus.FAILED.value, "error": error_message, "title": podcast_title, "podcast_id": None, } + receipt = make_receipt( + route="deliverables", + type="podcast", + operation="generate", + status="failed", + preview=podcast_title, + error=error_message, + ) + return with_receipt( + payload=payload, + receipt=receipt, + tool_call_id=runtime.tool_call_id, + ) return generate_podcast diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py index 385100c62..f12ca8a90 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py @@ -6,10 +6,14 @@ import logging import re from typing import Any +from langchain.tools import ToolRuntime from langchain_core.callbacks import dispatch_custom_event from langchain_core.messages import HumanMessage from langchain_core.tools import tool +from langgraph.types import Command +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import Report, shielded_async_session from app.services.connector_service import ConnectorService from app.services.llm_service import get_document_summary_llm @@ -573,13 +577,14 @@ def create_generate_report_tool( @tool async def generate_report( topic: str, + runtime: ToolRuntime, source_content: str = "", source_strategy: str = "provided", search_queries: list[str] | None = None, report_style: str = "detailed", user_instructions: str | None = None, parent_report_id: int | None = None, - ) -> dict[str, Any]: + ) -> Command: """ Generate a structured Markdown report artifact from provided content. @@ -692,6 +697,23 @@ def create_generate_report_tool( parent_report_content: str | None = None report_group_id: int | None = None + def _failed(payload: dict[str, Any], *, error: str) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="report", + operation="generate", + status="failed", + external_id=str(payload.get("report_id")) + if payload.get("report_id") is not None + else None, + preview=topic, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + async def _save_failed_report(error_msg: str) -> int | None: """Persist a failed report row using a short-lived session.""" try: @@ -753,12 +775,15 @@ def create_generate_report_tool( "No LLM configured. Please configure a language model in Settings." ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": topic, + }, + error=error_msg, + ) # Build the user instructions string user_instructions_section = "" @@ -971,12 +996,15 @@ def create_generate_report_tool( if not report_content or not isinstance(report_content, str): error_msg = "LLM returned empty or invalid content" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": topic, + }, + error=error_msg, + ) # LLMs often wrap output in ```markdown ... ``` fences — strip them report_content = _strip_wrapping_code_fences(report_content) @@ -984,12 +1012,15 @@ def create_generate_report_tool( if not report_content: error_msg = "LLM returned empty or invalid content" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": topic, + }, + error=error_msg, + ) # Strip any existing footer(s) carried over from parent version(s) while report_content.rstrip().endswith(_REPORT_FOOTER): @@ -1036,7 +1067,7 @@ def create_generate_report_tool( f"{metadata.get('section_count', 0)} sections" ) - return { + payload: dict[str, Any] = { "status": "ready", "report_id": saved_report_id, "title": topic, @@ -1045,17 +1076,32 @@ def create_generate_report_tool( "report_markdown": report_content, "message": f"Report generated successfully: {topic}", } + receipt = make_receipt( + route="deliverables", + type="report", + operation="generate", + status="success", + external_id=str(saved_report_id), + preview=topic, + ) + return with_receipt( + payload=payload, + receipt=receipt, + tool_call_id=runtime.tool_call_id, + ) except Exception as e: error_message = str(e) logger.exception(f"[generate_report] Error: {error_message}") report_id = await _save_failed_report(error_message) - - return { - "status": "failed", - "error": error_message, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_message, + "report_id": report_id, + "title": topic, + }, + error=error_message, + ) return generate_report diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py index ece3ce241..ad16b7ba7 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py @@ -8,10 +8,14 @@ from typing import Any import pypdf import typst +from langchain.tools import ToolRuntime from langchain_core.callbacks import dispatch_custom_event from langchain_core.messages import HumanMessage from langchain_core.tools import tool +from langgraph.types import Command +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import Report, shielded_async_session from app.services.llm_service import get_document_summary_llm @@ -429,10 +433,11 @@ def create_generate_resume_tool( @tool async def generate_resume( user_info: str, + runtime: ToolRuntime, user_instructions: str | None = None, parent_report_id: int | None = None, max_pages: int = 1, - ) -> dict[str, Any]: + ) -> Command: """ Generate a professional resume as a Typst document. @@ -476,6 +481,41 @@ def create_generate_resume_tool( template = _get_template() llm_reference = _build_llm_reference(template) + def _success(payload: dict[str, Any], *, report_id: int, title: str) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="resume", + operation="generate", + status="success", + external_id=str(report_id), + preview=title, + ), + tool_call_id=runtime.tool_call_id, + ) + + def _failed( + payload: dict[str, Any], + *, + report_id: int | None, + error: str, + title: str = "Resume", + ) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="resume", + operation="generate", + status="failed", + external_id=str(report_id) if report_id is not None else None, + preview=title, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + async def _save_failed_report(error_msg: str) -> int | None: try: async with shielded_async_session() as session: @@ -514,13 +554,17 @@ def create_generate_resume_tool( except ValueError as e: error_msg = str(e) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) # ── Phase 1: READ ───────────────────────────────────────────── async with shielded_async_session() as read_session: @@ -541,13 +585,17 @@ def create_generate_resume_tool( "No LLM configured. Please configure a language model in Settings." ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) # ── Phase 2: LLM GENERATION ─────────────────────────────────── @@ -588,13 +636,17 @@ def create_generate_resume_tool( if not body or not isinstance(body, str): error_msg = "LLM returned empty or invalid content" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) body = _strip_typst_fences(body) body = _strip_imports(body) @@ -661,13 +713,17 @@ def create_generate_resume_tool( f"{compile_error or 'Unknown compile error'}" ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) actual_pages = _count_pdf_pages(pdf_bytes) if actual_pages <= validated_max_pages: @@ -700,13 +756,17 @@ def create_generate_resume_tool( ): error_msg = "LLM returned empty content while compressing resume" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) body = _strip_typst_fences(compress_response.content) body = _strip_imports(body) @@ -718,13 +778,17 @@ def create_generate_resume_tool( f"Hard limit: <= {MAX_RESUME_PAGES} page(s), actual: {actual_pages}." ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) # ── Phase 4: SAVE ───────────────────────────────────────────── dispatch_custom_event( @@ -768,32 +832,40 @@ def create_generate_resume_tool( logger.info(f"[generate_resume] Created resume {saved_id}: {resume_title}") - return { - "status": "ready", - "report_id": saved_id, - "title": resume_title, - "content_type": "typst", - "is_revision": bool(parent_content), - "message": ( - f"Resume generated successfully: {resume_title}" - if target_page_met - else ( - f"Resume generated, but could not fit the target of <= {validated_max_pages} " - f"page(s). Final length: {actual_pages} page(s)." - ) - ), - } + return _success( + { + "status": "ready", + "report_id": saved_id, + "title": resume_title, + "content_type": "typst", + "is_revision": bool(parent_content), + "message": ( + f"Resume generated successfully: {resume_title}" + if target_page_met + else ( + f"Resume generated, but could not fit the target of <= {validated_max_pages} " + f"page(s). Final length: {actual_pages} page(s)." + ) + ), + }, + report_id=saved_id, + title=resume_title, + ) except Exception as e: error_message = str(e) logger.exception(f"[generate_resume] Error: {error_message}") report_id = await _save_failed_report(error_message) - return { - "status": "failed", - "error": error_message, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_message, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_message, + ) return generate_resume diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py index a9f3447ab..8c52293de 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py @@ -1,12 +1,29 @@ -"""Factory for a video-presentation tool that queues background work and returns an ID for polling.""" +"""Factory for a video-presentation tool. +Dispatches the heavy generation to Celery and then polls the +video-presentation row until it reaches a terminal status (READY/FAILED). +The tool always returns a real terminal ``Receipt`` — never a pending +one. The wait is bounded by the existing per-invocation safety net +(``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` in multi-agent mode, +HTTP / process lifetime in single-agent mode). Video rendering can be +heavy; raise that ceiling if your generations routinely exceed it. +""" + +import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import VideoPresentation, VideoPresentationStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_video_presentation_tool( search_space_id: int, @@ -19,9 +36,10 @@ def create_generate_video_presentation_tool( @tool async def generate_video_presentation( source_content: str, + runtime: ToolRuntime, video_title: str = "SurfSense Presentation", user_prompt: str | None = None, - ) -> dict[str, Any]: + ) -> Command: """Generate a video presentation from the provided content. Use this tool when the user asks to create a video, presentation, slides, or slide deck. @@ -56,25 +74,103 @@ def create_generate_video_presentation_tool( user_prompt=user_prompt, ) - print( - f"[generate_video_presentation] Created video presentation {video_pres_id}, task: {task.id}" + logger.info( + "[generate_video_presentation] Created video presentation %s, task: %s", + video_pres_id, + task.id, ) - return { - "status": VideoPresentationStatus.PENDING.value, + # Wait until the Celery worker flips the row to a terminal + # state. The wait is bounded only by the subagent invoke + # timeout (multi-agent) or HTTP lifetime (single-agent) — + # see app.agents.shared.deliverable_wait for details. + terminal_status, _columns, elapsed = await wait_for_deliverable( + model=VideoPresentation, + row_id=video_pres_id, + columns=[VideoPresentation.status], + terminal_statuses={ + VideoPresentationStatus.READY, + VideoPresentationStatus.FAILED, + }, + ) + + if terminal_status == VideoPresentationStatus.READY: + logger.info( + "[generate_video_presentation] %s READY in %.2fs", + video_pres_id, + elapsed, + ) + payload: dict[str, Any] = { + "status": VideoPresentationStatus.READY.value, + "video_presentation_id": video_pres_id, + "title": video_title, + "message": "Video presentation generated and saved.", + } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="video_presentation", + operation="generate", + status="success", + external_id=str(video_pres_id), + preview=video_title, + ), + tool_call_id=runtime.tool_call_id, + ) + + # Only other terminal state is FAILED. + logger.warning( + "[generate_video_presentation] %s FAILED in %.2fs", + video_pres_id, + elapsed, + ) + err = ( + "Background worker reported FAILED status for this " + "video presentation." + ) + payload = { + "status": VideoPresentationStatus.FAILED.value, "video_presentation_id": video_pres_id, "title": video_title, - "message": "Video presentation generation started. This may take a few minutes.", + "error": err, } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="video_presentation", + operation="generate", + status="failed", + external_id=str(video_pres_id), + preview=video_title, + error=err, + ), + tool_call_id=runtime.tool_call_id, + ) except Exception as e: error_message = str(e) - print(f"[generate_video_presentation] Error: {error_message}") - return { + logger.exception( + "[generate_video_presentation] Error: %s", error_message + ) + payload = { "status": VideoPresentationStatus.FAILED.value, "error": error_message, "title": video_title, "video_presentation_id": None, } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="video_presentation", + operation="generate", + status="failed", + preview=video_title, + error=error_message, + ), + tool_call_id=runtime.tool_call_id, + ) return generate_video_presentation diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md index 2ae21c271..c4e36fc73 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md @@ -150,11 +150,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: +<include snippet="output_contract_base"/> + +Route-specific rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. - `evidence.content_excerpt`: max ~500 characters. Surface a short excerpt or a one-sentence summary, not the full file body. The supervisor already sees the tool's raw output. +<include snippet="verifiable_handle"/> + Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md index b0f2dacb2..25dafa3df 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md @@ -117,11 +117,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: +<include snippet="output_contract_base"/> + +Route-specific rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. - `evidence.content_excerpt`: max ~500 characters. Surface a short excerpt or a one-sentence summary, not the full file body. The supervisor already sees the tool's raw output. +<include snippet="verifiable_handle"/> + Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md index 13f7b68a5..b656c5019 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md @@ -6,7 +6,7 @@ Persist durable preferences/facts/instructions with `update_memory` while avoidi </goal> <visibility_scope> -{{MEMORY_VISIBILITY_POLICY}} +Memory is search-space-scoped; do not assume cross-workspace visibility. </visibility_scope> <available_tools> @@ -53,10 +53,8 @@ Return **only** one JSON object (no markdown/prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` -> `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` -> `next_step` must be non-null. -- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - `evidence.memory_category` is a semantic classification for supervisor logs only. It is not the persisted storage format and must not force inline `[fact|preference|instruction]` markers into saved memory. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md index f1a22ddf1..3eabd8ee0 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md @@ -46,10 +46,8 @@ Return **only** one JSON object (no markdown/prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` -> `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` -> `next_step` must be non-null. -- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - `evidence.findings`: max 10 entries, each a single sentence stating one distinct fact. Do not paste raw paragraphs, scraped pages, or quote blocks. - `evidence.sources`: max 10 URLs, one per finding when applicable. List each URL once. </output_contract> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md index 9434db7a1..e6a639af3 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md @@ -92,12 +92,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: base, table, field, choice, record, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (record id, primary-field value, and 1-2 most relevant fields; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers, choice IDs, or required fields. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md index a663f5b37..9168f4d2b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md @@ -111,11 +111,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +Route-specific rules: - For `search_calendar_events` results, set `evidence.items` to `{ "total": N }` and list the matched events in `action_summary` (title, date, start time; up to 10 entries, then `"...and N more"`). - For ambiguous matches across `update_calendar_event` / `delete_calendar_event`, populate `evidence.matched_candidates` with up to 5 options (`id` + `label`, where `label` should include the event title and start time for human readability). +<include snippet="verifiable_handle"/> + Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md index 898197f14..029609670 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md @@ -93,12 +93,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: task, list, member, status, custom-field choice, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (task id, title, status, assignees; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers, list statuses, or assignees. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md index 991ec3d03..5aa687cd0 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md @@ -100,9 +100,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md index 249f9ec8b..aaabd2ac3 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md @@ -108,9 +108,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Resolve before you call; verify before you send; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md index a963b0ec6..8e498dfdf 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md @@ -98,9 +98,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md index c04d69ad0..02aff5589 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md @@ -110,11 +110,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +Route-specific rules: - For `search_gmail` results, set `evidence.items` to `{ "total": N }` and list the matched emails in `action_summary` (sender, subject, date; up to 10 entries, then `"...and N more"`). - For ambiguous matches across `update_gmail_draft` / `trash_gmail_email` / `read_gmail_email`, populate `evidence.matched_candidates` with up to 5 options (`id` + `label`). +<include snippet="verifiable_handle"/> + Infer before you call; verify before you send; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py index 578233b57..0680e51cb 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py @@ -5,12 +5,16 @@ from datetime import datetime from email.mime.text import MIMEText from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( request_approval, ) +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.services.gmail import GmailToolMetadataService logger = logging.getLogger(__name__) @@ -26,9 +30,10 @@ def create_send_gmail_email_tool( to: str, subject: str, body: str, + runtime: ToolRuntime, cc: str | None = None, bcc: str | None = None, - ) -> dict[str, Any]: + ) -> Command: """Send an email via Gmail. Use when the user explicitly asks to send an email. This sends the @@ -60,11 +65,34 @@ def create_send_gmail_email_tool( """ logger.info(f"send_gmail_email called: to='{to}', subject='{subject}'") + def _emit( + payload: dict[str, Any], + *, + success: bool, + external_id: str | None = None, + error: str | None = None, + ) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="gmail", + type="message", + operation="send", + status="success" if success else "failed", + external_id=external_id, + preview=f"to={to}: {subject}"[:200], + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + if db_session is None or search_space_id is None or user_id is None: - return { - "status": "error", - "message": "Gmail tool not properly configured. Please contact support.", - } + msg = "Gmail tool not properly configured. Please contact support." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) try: metadata_service = GmailToolMetadataService(db_session) @@ -74,16 +102,24 @@ def create_send_gmail_email_tool( if "error" in context: logger.error(f"Failed to fetch creation context: {context['error']}") - return {"status": "error", "message": context["error"]} + return _emit( + {"status": "error", "message": context["error"]}, + success=False, + error=context["error"], + ) accounts = context.get("accounts", []) if accounts and all(a.get("auth_expired") for a in accounts): logger.warning("All Gmail accounts have expired authentication") - return { - "status": "auth_error", - "message": "All connected Gmail accounts need re-authentication. Please re-authenticate in your connector settings.", - "connector_type": "gmail", - } + return _emit( + { + "status": "auth_error", + "message": "All connected Gmail accounts need re-authentication. Please re-authenticate in your connector settings.", + "connector_type": "gmail", + }, + success=False, + error="auth_expired", + ) logger.info( f"Requesting approval for sending Gmail email: to='{to}', subject='{subject}'" @@ -103,10 +139,14 @@ def create_send_gmail_email_tool( ) if result.rejected: - return { - "status": "rejected", - "message": "User declined. The email was not sent. Do not ask again or suggest alternatives.", - } + return _emit( + { + "status": "rejected", + "message": "User declined. The email was not sent. Do not ask again or suggest alternatives.", + }, + success=False, + error="user_rejected", + ) final_to = result.params.get("to", to) final_subject = result.params.get("subject", subject) @@ -135,10 +175,14 @@ def create_send_gmail_email_tool( ) connector = result.scalars().first() if not connector: - return { - "status": "error", - "message": "Selected Gmail connector is invalid or has been disconnected.", - } + msg = ( + "Selected Gmail connector is invalid or has been disconnected." + ) + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) actual_connector_id = connector.id else: result = await db_session.execute( @@ -150,10 +194,12 @@ def create_send_gmail_email_tool( ) connector = result.scalars().first() if not connector: - return { - "status": "error", - "message": "No Gmail connector found. Please connect Gmail in your workspace settings.", - } + msg = "No Gmail connector found. Please connect Gmail in your workspace settings." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) actual_connector_id = connector.id logger.info( @@ -166,10 +212,12 @@ def create_send_gmail_email_tool( ): cca_id = connector.config.get("composio_connected_account_id") if not cca_id: - return { - "status": "error", - "message": "Composio connected account ID not found for this Gmail connector.", - } + msg = "Composio connected account ID not found for this Gmail connector." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) from app.services.composio_service import ComposioService @@ -187,7 +235,11 @@ def create_send_gmail_email_tool( bcc=final_bcc, ) if error: - return {"status": "error", "message": error} + return _emit( + {"status": "error", "message": error}, + success=False, + error=error, + ) sent = {"id": sent_message_id, "threadId": sent_thread_id} else: from google.oauth2.credentials import Credentials @@ -275,11 +327,15 @@ def create_send_gmail_email_tool( actual_connector_id, exc_info=True, ) - return { - "status": "insufficient_permissions", - "connector_id": actual_connector_id, - "message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.", - } + return _emit( + { + "status": "insufficient_permissions", + "connector_id": actual_connector_id, + "message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.", + }, + success=False, + error="insufficient_permissions", + ) raise logger.info( @@ -310,12 +366,16 @@ def create_send_gmail_email_tool( logger.warning(f"KB sync after send failed: {kb_err}") kb_message_suffix = " This email will be added to your knowledge base in the next scheduled sync." - return { - "status": "success", - "message_id": sent.get("id"), - "thread_id": sent.get("threadId"), - "message": f"Successfully sent email to '{final_to}' with subject '{final_subject}'.{kb_message_suffix}", - } + return _emit( + { + "status": "success", + "message_id": sent.get("id"), + "thread_id": sent.get("threadId"), + "message": f"Successfully sent email to '{final_to}' with subject '{final_subject}'.{kb_message_suffix}", + }, + success=True, + external_id=sent.get("id"), + ) except Exception as e: from langgraph.errors import GraphInterrupt @@ -324,9 +384,11 @@ def create_send_gmail_email_tool( raise logger.error(f"Error sending Gmail email: {e}", exc_info=True) - return { - "status": "error", - "message": "Something went wrong while sending the email. Please try again.", - } + msg = "Something went wrong while sending the email. Please try again." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=str(e), + ) return send_gmail_email diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md index b78e1f7c6..10140d842 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md @@ -100,9 +100,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md index 4dcc56454..d7816dead 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md @@ -111,12 +111,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: site, project, issue, user, transition, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (issue key, summary, status, assignee; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers, transitions, or required fields. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md index 1d96a4105..5dfd29112 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md @@ -101,12 +101,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: issue, user, project, state, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (identifier, title, state, assignee; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md index 0f42161b3..e483789d5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md @@ -101,9 +101,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; verify before you create; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md index b38c30167..909c72471 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md @@ -99,9 +99,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py index 85d0ef22e..c98b25811 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py @@ -1,12 +1,16 @@ import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( request_approval, ) +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector from app.services.notion.tool_metadata_service import NotionToolMetadataService @@ -35,8 +39,9 @@ def create_delete_notion_page_tool( @tool async def delete_notion_page( page_title: str, + runtime: ToolRuntime, delete_from_kb: bool = False, - ) -> dict[str, Any]: + ) -> Command: """Delete (archive) a Notion page. Use this tool when the user asks you to delete, remove, or archive @@ -65,14 +70,39 @@ def create_delete_notion_page_tool( f"delete_notion_page called: page_title='{page_title}', delete_from_kb={delete_from_kb}" ) + def _emit( + payload: dict[str, Any], + *, + status: str, + external_id: str | None = None, + error: str | None = None, + ) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="notion", + type="page", + operation="delete", + status="success" if status == "success" else "failed", + external_id=external_id, + preview=page_title, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + if db_session is None or search_space_id is None or user_id is None: logger.error( "Notion tool not properly configured - missing required parameters" ) - return { - "status": "error", - "message": "Notion tool not properly configured. Please contact support.", - } + return _emit( + { + "status": "error", + "message": "Notion tool not properly configured. Please contact support.", + }, + status="error", + error="Notion tool not properly configured. Please contact support.", + ) try: # Get page context (page_id, account, title) from indexed data @@ -86,16 +116,18 @@ def create_delete_notion_page_tool( # Check if it's a "not found" error (softer handling for LLM) if "not found" in error_msg.lower(): logger.warning(f"Page not found: {error_msg}") - return { - "status": "not_found", - "message": error_msg, - } + return _emit( + {"status": "not_found", "message": error_msg}, + status="error", + error=error_msg, + ) else: logger.error(f"Failed to fetch delete context: {error_msg}") - return { - "status": "error", - "message": error_msg, - } + return _emit( + {"status": "error", "message": error_msg}, + status="error", + error=error_msg, + ) account = context.get("account", {}) if account.get("auth_expired"): @@ -103,10 +135,14 @@ def create_delete_notion_page_tool( "Notion account %s has expired authentication", account.get("id"), ) - return { - "status": "auth_error", - "message": "The Notion account for this page needs re-authentication. Please re-authenticate in your connector settings.", - } + return _emit( + { + "status": "auth_error", + "message": "The Notion account for this page needs re-authentication. Please re-authenticate in your connector settings.", + }, + status="error", + error="auth_expired", + ) page_id = context.get("page_id") connector_id_from_context = account.get("id") @@ -129,10 +165,14 @@ def create_delete_notion_page_tool( if result.rejected: logger.info("Notion page deletion rejected by user") - return { - "status": "rejected", - "message": "User declined. Do not retry or suggest alternatives.", - } + return _emit( + { + "status": "rejected", + "message": "User declined. Do not retry or suggest alternatives.", + }, + status="error", + error="user_rejected", + ) final_page_id = result.params.get("page_id", page_id) final_connector_id = result.params.get( @@ -165,18 +205,26 @@ def create_delete_notion_page_tool( logger.error( f"Invalid connector_id={final_connector_id} for search_space_id={search_space_id}" ) - return { - "status": "error", - "message": "Selected Notion account is invalid or has been disconnected. Please select a valid account.", - } + return _emit( + { + "status": "error", + "message": "Selected Notion account is invalid or has been disconnected. Please select a valid account.", + }, + status="error", + error="invalid_connector", + ) actual_connector_id = connector.id logger.info(f"Validated Notion connector: id={actual_connector_id}") else: logger.error("No connector found for this page") - return { - "status": "error", - "message": "No connector found for this page.", - } + return _emit( + { + "status": "error", + "message": "No connector found for this page.", + }, + status="error", + error="no_connector", + ) # Create connector instance notion_connector = NotionHistoryConnector( @@ -232,7 +280,13 @@ def create_delete_notion_page_tool( f"{result.get('message', '')} (also removed from knowledge base)" ) - return result + status = result.get("status", "error") + return _emit( + result, + status=status, + external_id=str(final_page_id) if final_page_id else None, + error=None if status == "success" else result.get("message"), + ) except Exception as e: from langgraph.errors import GraphInterrupt @@ -245,20 +299,28 @@ def create_delete_notion_page_tool( if isinstance(e, NotionAPIError) and ( "401" in error_str or "unauthorized" in error_str ): - return { - "status": "auth_error", - "message": str(e), - "connector_id": connector_id_from_context - if "connector_id_from_context" in dir() - else None, - "connector_type": "notion", - } + return _emit( + { + "status": "auth_error", + "message": str(e), + "connector_id": connector_id_from_context + if "connector_id_from_context" in dir() + else None, + "connector_type": "notion", + }, + status="error", + error=str(e), + ) if isinstance(e, ValueError | NotionAPIError): message = str(e) else: message = ( "Something went wrong while deleting the page. Please try again." ) - return {"status": "error", "message": message} + return _emit( + {"status": "error", "message": message}, + status="error", + error=message, + ) return delete_notion_page diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md index 8ae444a58..4b45b05a9 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md @@ -97,9 +97,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md index 3c24b19c9..e4e0d1f6f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md @@ -87,12 +87,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: channel, user, message, thread). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (channel/user, key identifier, timestamp, short snippet; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you post; never guess channel, user, or thread targets. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md index c3a280f79..9b283acf5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md @@ -115,9 +115,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Resolve before you call; verify before you send; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py index 2fce413a6..5694e4326 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py @@ -2,8 +2,11 @@ from __future__ import annotations +from functools import lru_cache from importlib import resources +_SHARED_SNIPPETS_PACKAGE = "app.agents.multi_agent_chat.subagents.shared.snippets" + def read_md_file(package: str, stem: str) -> str: """Load ``{stem}.md`` from ``package`` via importlib resources, or return empty.""" @@ -12,3 +15,13 @@ def read_md_file(package: str, stem: str) -> str: return "" text = ref.read_text(encoding="utf-8") return text.rstrip("\n") + + +@lru_cache(maxsize=64) +def read_shared_snippet(name: str) -> str: + """Load a shared markdown snippet from the snippets package. + + Cached because snippets are static at runtime and resolved many times + (once per subagent build, plus per-subagent-per-route). + """ + return read_md_file(_SHARED_SNIPPETS_PACKAGE, name) diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py new file mode 100644 index 000000000..802a8e241 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py @@ -0,0 +1,6 @@ +"""Shared markdown snippets composed into every subagent system prompt. + +Resolved at build time by :func:`pack_subagent` in ``subagent_builder.py`` +via the ``<include snippet="NAME"/>`` directive. See ``output_contract_base.md`` +and ``verifiable_handle.md`` for the included content. +""" diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md new file mode 100644 index 000000000..100daae75 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md @@ -0,0 +1,6 @@ +Rules (universal): +- `status=success` -> `next_step=null`, `missing_fields=null`. +- `status=partial|blocked|error` -> `next_step` must be non-null. +- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +- `assumptions`: any inferences you made about the user's intent; `null` when no inferences were needed. +- The `evidence` object's fields are documented in your route-specific `<output_contract>` above; never invent fields the tool did not return. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md new file mode 100644 index 000000000..bea070ce9 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md @@ -0,0 +1,10 @@ +<verifiable_handle> +Mutating tools you call return a structured `Receipt` object alongside their normal payload (see `evidence.receipts` in your `<output_contract>`). The supervisor uses the Receipt's `verifiable_url` and `external_id` to independently confirm the operation succeeded - do not paraphrase, shorten, or guess these values. + +Rules: +- Quote each Receipt's `verifiable_url` and `external_id` **verbatim** in `evidence.receipts`. Copy character-for-character; never retype from memory. +- If a Receipt has `status="failed"`, set your own `status="error"` and put the Receipt's `error` field in `next_step`. +- If a Receipt has `status="pending"` (async backends — podcasts, video presentations, anything queued through Celery), report `status=success`, surface the pending Receipt as-is, and tell the supervisor in `action_summary` that the artefact is **being generated in the background** (e.g. "Podcast 38 queued; orchestrator should report it as kicked off, not yet ready"). A pending Receipt almost always lacks `verifiable_url` because the artefact does not exist yet — that is expected, not a defect. Do **not** wait, poll, or retry; control returns to the supervisor immediately and the asset becomes visible to the user out of band via its own UI surface. +- Never claim a mutation succeeded without a matching Receipt with `status="success"` or `"pending"` in your tool results this turn. +- For tools that do not return a Receipt (read-only operations, search, lookup), the receipt rules do not apply; only the route-specific `evidence` fields matter. +</verifiable_handle> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py index 797ab535b..f891f94d2 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py @@ -2,12 +2,30 @@ from __future__ import annotations +from collections.abc import Callable, Mapping from dataclasses import dataclass +from typing import Any from deepagents import SubAgent from app.agents.new_chat.permissions import Ruleset +# A context-hint provider receives the parent-agent ``runtime.state`` mapping +# and the ``description`` the orchestrator wrote, and returns a short string +# the runtime prepends to the subagent's first ``HumanMessage``. Used for +# things like "current search-space id is X" or "the user is in workspace Y" — +# never for full corpora, since the prepended text consumes the subagent's +# prompt budget on every invocation. Return ``None`` (or an empty string) to +# skip the hint for this call. +ContextHintProvider = Callable[[Mapping[str, Any], str], str | None] + +# Custom key stashed on the deepagents ``SubAgent`` dict so the provider +# survives the trip from ``pack_subagent`` → registry → middleware → +# task_tool. ``deepagents.create_agent`` only extracts the keys it +# recognises, so an extra key here is dropped silently at compile time. +# The prefix avoids any collision with future deepagents fields. +SURF_CONTEXT_HINT_PROVIDER_KEY = "surf_context_hint_provider" + @dataclass(frozen=True, slots=True) class SurfSenseSubagentSpec: @@ -20,10 +38,22 @@ class SurfSenseSubagentSpec: layers them into the subagent's :class:`PermissionMiddleware`, so each subagent owns its own ruleset without aliasing the shared rule engine. + context_hint_provider: Optional callback invoked once per ``task(...)`` + invocation, immediately before the subagent runs. Its return + value is prepended to the subagent's first ``HumanMessage`` so + the subagent can see things it would otherwise have to discover + (active search space, KB root, current user timezone, etc.). + Kept out of the deepagents ``spec`` because that dict is forwarded + verbatim to upstream code and only recognises its own typed keys. """ spec: SubAgent ruleset: Ruleset + context_hint_provider: ContextHintProvider | None = None -__all__ = ["SurfSenseSubagentSpec"] +__all__ = [ + "SURF_CONTEXT_HINT_PROVIDER_KEY", + "ContextHintProvider", + "SurfSenseSubagentSpec", +] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py index 7173901f9..5025b32e7 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py @@ -2,6 +2,8 @@ from __future__ import annotations +import logging +import re from typing import Any, cast from deepagents import SubAgent @@ -12,9 +14,48 @@ from langchain_core.tools import BaseTool from app.agents.multi_agent_chat.middleware.shared.permissions import ( build_permission_mw, ) -from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec +from app.agents.multi_agent_chat.subagents.shared.md_file_reader import ( + read_shared_snippet, +) +from app.agents.multi_agent_chat.subagents.shared.spec import ( + SURF_CONTEXT_HINT_PROVIDER_KEY, + ContextHintProvider, + SurfSenseSubagentSpec, +) from app.agents.new_chat.permissions import Ruleset +logger = logging.getLogger(__name__) + +# ``<include snippet="NAME"/>`` directive. Matches an XML-style self-closing +# tag whose ``snippet`` attribute names a file in ``shared/snippets/``. +# Whitespace around the attribute and self-close is tolerated; the snippet +# name itself must be a bare identifier (letters / digits / underscores) so +# we never pull a path-traversal value into ``read_shared_snippet``. +_INCLUDE_DIRECTIVE_RE = re.compile( + r"<include\s+snippet=\"(?P<name>[A-Za-z0-9_]+)\"\s*/>" +) + + +def _resolve_includes(prompt: str, *, subagent_name: str) -> str: + """Replace ``<include snippet="X"/>`` directives with the snippet body. + + Unknown snippet names raise; an empty body is treated as unknown so a + typo or missing file fails loudly at startup instead of silently + shipping a broken prompt to the LLM. + """ + + def _replace(match: re.Match[str]) -> str: + name = match.group("name") + body = read_shared_snippet(name) + if not body.strip(): + raise ValueError( + f"Subagent {subagent_name!r}: unknown or empty shared " + f"snippet {name!r} referenced via <include>." + ) + return body + + return _INCLUDE_DIRECTIVE_RE.sub(_replace, prompt) + def _user_allowlist_for( dependencies: dict[str, Any], subagent_name: str @@ -43,6 +84,7 @@ def pack_subagent( dependencies: dict[str, Any], model: BaseChatModel | None = None, middleware_stack: dict[str, Any] | None = None, + context_hint_provider: ContextHintProvider | None = None, ) -> SurfSenseSubagentSpec: """Pack the route-local pieces into one sub-agent spec + its Ruleset. @@ -68,6 +110,8 @@ def pack_subagent( msg = f"Subagent {name!r}: system_prompt is empty" raise ValueError(msg) + system_prompt = _resolve_includes(system_prompt, subagent_name=name) + flags = dependencies["flags"] user_allowlist = _user_allowlist_for(dependencies, name) subagent_rulesets: list[Ruleset] = [ruleset] @@ -99,4 +143,12 @@ def pack_subagent( } if model is not None: spec_dict["model"] = model - return SurfSenseSubagentSpec(spec=cast(SubAgent, spec_dict), ruleset=ruleset) + if context_hint_provider is not None: + # Stash the callback on the dict so it survives the trip through + # registry / middleware unpacking (both treat the spec as opaque). + spec_dict[SURF_CONTEXT_HINT_PROVIDER_KEY] = context_hint_provider + return SurfSenseSubagentSpec( + spec=cast(SubAgent, spec_dict), + ruleset=ruleset, + context_hint_provider=context_hint_provider, + ) diff --git a/surfsense_backend/app/agents/new_chat/filesystem_state.py b/surfsense_backend/app/agents/new_chat/filesystem_state.py index cc674be76..de2c94b41 100644 --- a/surfsense_backend/app/agents/new_chat/filesystem_state.py +++ b/surfsense_backend/app/agents/new_chat/filesystem_state.py @@ -33,9 +33,11 @@ from typing_extensions import TypedDict from app.agents.new_chat.state_reducers import ( _add_unique_reducer, _dict_merge_with_tombstones_reducer, + _int_counter_merge_reducer, _list_append_reducer, _replace_reducer, ) +from app.agents.shared.receipt import Receipt class PendingMove(TypedDict, total=False): @@ -172,6 +174,35 @@ class SurfSenseFilesystemState(FilesystemState): workspace_tree_text: NotRequired[Annotated[str, _replace_reducer]] """Pre-rendered ``<workspace_tree>`` body; shared with subagents to skip re-render.""" + billable_calls: NotRequired[Annotated[dict[str, int], _int_counter_merge_reducer]] + """Per-subagent ``task(...)`` invocation counter, summed across the turn. + + Incremented by ``task_tool.py`` each time a subagent invocation + completes (single- or batch-mode). The orchestrator can read this map + to self-limit when a runaway loop sends the same specialist 20 calls + in a row; the runtime emits a soft warning ToolMessage once the + cumulative count crosses :data:`DEFAULT_SUBAGENT_BILLABLE_THRESHOLD`. + Cleared by checkpoint rollover (i.e. per turn). + """ + + receipts: NotRequired[Annotated[list[Receipt], _list_append_reducer]] + """Structured Receipt handles emitted by mutating subagent tools this turn. + + Each mutating tool (deliverables, every connector, KB writes via the + persistence middleware) wraps its native return into a + :class:`~app.agents.shared.receipt.Receipt` + and returns it under the ``"receipt"`` key alongside its existing + payload. The subagent's tool-call middleware folds the receipt into + this list, and ``_return_command_with_state_update`` in + ``checkpointed_subagent_middleware/task_tool.py`` carries the list up + to the parent automatically (``"receipts"`` is not in + ``EXCLUDED_STATE_KEYS``). + + Append-only across the turn; cleared by checkpoint rollover. The + orchestrator reads it via the ``<verification>`` teaching to confirm + side-effecting subagent claims (see ``shared/snippets/verifiable_handle.md``). + """ + __all__ = [ "KbAnonDoc", diff --git a/surfsense_backend/app/agents/new_chat/middleware/compaction.py b/surfsense_backend/app/agents/new_chat/middleware/compaction.py index 8173976fe..f8d340e5d 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/compaction.py +++ b/surfsense_backend/app/agents/new_chat/middleware/compaction.py @@ -34,8 +34,7 @@ from deepagents.middleware.summarization import ( ) from langchain_core.messages import SystemMessage -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot if TYPE_CHECKING: from deepagents.backends.protocol import BACKEND_TYPES diff --git a/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py b/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py index d50cadc0e..a7901c010 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py +++ b/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py @@ -47,8 +47,7 @@ from langgraph.config import get_config from langgraph.runtime import Runtime from langgraph.types import interrupt -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py b/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py index cc30f4897..c88dced85 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py +++ b/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py @@ -55,6 +55,7 @@ from app.agents.new_chat.path_resolver import ( virtual_path_to_doc, ) from app.agents.new_chat.state_reducers import _CLEAR +from app.agents.shared.receipt import Receipt, make_receipt from app.db import ( AgentActionLog, Chunk, @@ -1392,6 +1393,81 @@ async def commit_staged_filesystem_state( "pending_dir_deletes": [_CLEAR], "dirty_path_tool_calls": {_CLEAR: True}, } + + # Emit one Receipt per committed mutation, folded into ``state['receipts']`` + # via ``_list_append_reducer``. The receipts surface what actually committed + # (post-savepoint) rather than what the LLM intended; the orchestrator uses + # them as ground truth in the ``<verification>`` teaching. KB writes do not + # have public verifiable URLs, so ``verifiable_url`` stays unset. + receipts: list[Receipt] = [] + + def _kb_receipt( + *, + type: str, + operation: str, + path: str, + external_id: int | None = None, + ) -> None: + if not path: + return + preview = path.rsplit("/", 1)[-1] or path + receipts.append( + make_receipt( + route="knowledge_base", + type=type, + operation=operation, + status="success", + external_id=str(external_id) if external_id is not None else path, + preview=preview, + ) + ) + + for payload in committed_creates: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="write_file", + path=path, + external_id=payload.get("id"), + ) + for payload in committed_updates: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="edit_file", + path=path, + external_id=payload.get("id"), + ) + for payload in applied_moves: + # ``applied_moves`` rows carry the destination ``virtualPath`` because + # the move has already landed in the DB by the time we reach this code. + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="move_file", + path=path, + external_id=payload.get("id"), + ) + for path in staged_dirs: + _kb_receipt(type="folder", operation="mkdir", path=path) + for payload in committed_deletes: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="rm", + path=path, + external_id=payload.get("id"), + ) + for payload in committed_folder_deletes: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="folder", + operation="rmdir", + path=path, + external_id=payload.get("id"), + ) + if receipts: + delta["receipts"] = receipts files_delta: dict[str, Any] = {} if temp_paths: files_delta.update(dict.fromkeys(temp_paths)) diff --git a/surfsense_backend/app/agents/new_chat/middleware/permission.py b/surfsense_backend/app/agents/new_chat/middleware/permission.py index e174ab0bd..07549bedb 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/permission.py +++ b/surfsense_backend/app/agents/new_chat/middleware/permission.py @@ -61,8 +61,7 @@ from app.agents.new_chat.permissions import ( aggregate_action, evaluate_many, ) -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/new_chat/state_reducers.py b/surfsense_backend/app/agents/new_chat/state_reducers.py index 89fc86367..c7b7685f0 100644 --- a/surfsense_backend/app/agents/new_chat/state_reducers.py +++ b/surfsense_backend/app/agents/new_chat/state_reducers.py @@ -171,6 +171,39 @@ def _dict_merge_with_tombstones_reducer( return result +def _int_counter_merge_reducer( + left: dict[str, int] | None, + right: dict[str, int] | None, +) -> dict[str, int]: + """Merge ``right`` into ``left`` by **summing** per-key integer counters. + + Used for state fields that accumulate counts across multiple updates + within the same turn (e.g. per-subagent ``billable_calls``). Unknown + keys are added; existing keys are summed. ``_CLEAR`` sentinels reset + the accumulator the same way the other reducers do, so the orchestrator + can wipe the counter at end-of-turn if needed. + """ + if right is None: + return dict(left or {}) + + if _CLEAR in right or any(_is_clear(k) for k in right): + result: dict[str, int] = {} + for key, value in right.items(): + if _is_clear(key): + continue + if not isinstance(value, int): + continue + result[key] = result.get(key, 0) + value + return result + + base = dict(left or {}) + for key, value in right.items(): + if not isinstance(value, int): + continue + base[key] = base.get(key, 0) + value + return base + + def _initial_filesystem_state() -> dict[str, Any]: """Default empty values for SurfSense filesystem state fields. @@ -200,6 +233,7 @@ __all__ = [ "_add_unique_reducer", "_dict_merge_with_tombstones_reducer", "_initial_filesystem_state", + "_int_counter_merge_reducer", "_list_append_reducer", "_replace_reducer", ] diff --git a/surfsense_backend/app/agents/new_chat/tools/podcast.py b/surfsense_backend/app/agents/new_chat/tools/podcast.py index 2c9b7fa0c..36aecfe49 100644 --- a/surfsense_backend/app/agents/new_chat/tools/podcast.py +++ b/surfsense_backend/app/agents/new_chat/tools/podcast.py @@ -2,17 +2,23 @@ Podcast generation tool for the SurfSense agent. This module provides a factory function for creating the generate_podcast tool -that submits a Celery task for background podcast generation. The frontend -polls for completion and auto-updates when the podcast is ready. +that submits a Celery task for background podcast generation. The tool then +polls the podcast row until it reaches a terminal status (READY/FAILED) and +returns that status. The wait is bounded by the chat's HTTP / process +lifetime; see app.agents.shared.deliverable_wait for details. """ +import logging from typing import Any from langchain_core.tools import tool from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable from app.db import Podcast, PodcastStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_podcast_tool( search_space_id: int, @@ -97,18 +103,57 @@ def create_generate_podcast_tool( user_prompt=user_prompt, ) - print(f"[generate_podcast] Created podcast {podcast_id}, task: {task.id}") + logger.info( + "[generate_podcast] Created podcast %s, task: %s", + podcast_id, + task.id, + ) + # Wait until the Celery worker flips the row to a terminal + # state. No internal budget — see deliverable_wait module. + terminal_status, columns, elapsed = await wait_for_deliverable( + model=Podcast, + row_id=podcast_id, + columns=[Podcast.status, Podcast.file_location], + terminal_statuses={PodcastStatus.READY, PodcastStatus.FAILED}, + ) + + if terminal_status == PodcastStatus.READY: + file_location = columns[1] if columns else None + logger.info( + "[generate_podcast] Podcast %s READY in %.2fs (file=%s)", + podcast_id, + elapsed, + file_location, + ) + return { + "status": PodcastStatus.READY.value, + "podcast_id": podcast_id, + "title": podcast_title, + "file_location": file_location, + "message": ( + "Podcast generated and saved to your podcast panel." + ), + } + + # Only other terminal state is FAILED. + logger.warning( + "[generate_podcast] Podcast %s FAILED in %.2fs", + podcast_id, + elapsed, + ) return { - "status": PodcastStatus.PENDING.value, + "status": PodcastStatus.FAILED.value, "podcast_id": podcast_id, "title": podcast_title, - "message": "Podcast generation started. This may take a few minutes.", + "error": ( + "Background worker reported FAILED status for this podcast." + ), } except Exception as e: error_message = str(e) - print(f"[generate_podcast] Error: {error_message}") + logger.exception("[generate_podcast] Error: %s", error_message) return { "status": PodcastStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py index 7bf9a1c3b..4bf13b28e 100644 --- a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py +++ b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py @@ -2,17 +2,23 @@ Video presentation generation tool for the SurfSense agent. This module provides a factory function for creating the generate_video_presentation -tool that submits a Celery task for background video presentation generation. -The frontend polls for completion and auto-updates when the presentation is ready. +tool that submits a Celery task for background video presentation generation. The +tool then polls the row until it reaches a terminal status (READY/FAILED) and +returns that status. The wait is bounded by the chat's HTTP / process lifetime; +see app.agents.shared.deliverable_wait for details. """ +import logging from typing import Any from langchain_core.tools import tool from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable from app.db import VideoPresentation, VideoPresentationStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_video_presentation_tool( search_space_id: int, @@ -72,20 +78,58 @@ def create_generate_video_presentation_tool( user_prompt=user_prompt, ) - print( - f"[generate_video_presentation] Created video presentation {video_pres_id}, task: {task.id}" + logger.info( + "[generate_video_presentation] Created video presentation %s, task: %s", + video_pres_id, + task.id, ) + # Wait until the Celery worker flips the row to a terminal + # state. No internal budget — see deliverable_wait module. + terminal_status, _columns, elapsed = await wait_for_deliverable( + model=VideoPresentation, + row_id=video_pres_id, + columns=[VideoPresentation.status], + terminal_statuses={ + VideoPresentationStatus.READY, + VideoPresentationStatus.FAILED, + }, + ) + + if terminal_status == VideoPresentationStatus.READY: + logger.info( + "[generate_video_presentation] %s READY in %.2fs", + video_pres_id, + elapsed, + ) + return { + "status": VideoPresentationStatus.READY.value, + "video_presentation_id": video_pres_id, + "title": video_title, + "message": "Video presentation generated and saved.", + } + + # Only other terminal state is FAILED. + logger.warning( + "[generate_video_presentation] %s FAILED in %.2fs", + video_pres_id, + elapsed, + ) return { - "status": VideoPresentationStatus.PENDING.value, + "status": VideoPresentationStatus.FAILED.value, "video_presentation_id": video_pres_id, "title": video_title, - "message": "Video presentation generation started. This may take a few minutes.", + "error": ( + "Background worker reported FAILED status for this " + "video presentation." + ), } except Exception as e: error_message = str(e) - print(f"[generate_video_presentation] Error: {error_message}") + logger.exception( + "[generate_video_presentation] Error: %s", error_message + ) return { "status": VideoPresentationStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/agents/shared/__init__.py b/surfsense_backend/app/agents/shared/__init__.py new file mode 100644 index 000000000..7c46c65ff --- /dev/null +++ b/surfsense_backend/app/agents/shared/__init__.py @@ -0,0 +1,9 @@ +"""Cross-package agent contracts. + +Symbols here are intentionally framework-light (no LangGraph / deepagents +internals) so they can be imported from both ``app.agents.new_chat`` and +``app.agents.multi_agent_chat`` without creating a circular dependency +between the two packages. See ``receipt.py`` for the rationale. +""" + +from __future__ import annotations diff --git a/surfsense_backend/app/agents/shared/deliverable_wait.py b/surfsense_backend/app/agents/shared/deliverable_wait.py new file mode 100644 index 000000000..abaa017ea --- /dev/null +++ b/surfsense_backend/app/agents/shared/deliverable_wait.py @@ -0,0 +1,123 @@ +"""Shared poll-until-terminal helper for Celery-backed deliverables. + +Lives in ``app.agents.shared`` (neutral package, no dependencies on either +``new_chat`` or ``multi_agent_chat``) so both the flat single-agent tools +under ``app/agents/new_chat/tools/`` and the multi-agent subagent tools +under ``app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/`` +can import it without creating a circular dependency. + +Background +---------- +Tools like ``generate_podcast`` and ``generate_video_presentation`` enqueue +the heavy work to Celery and historically returned immediately with a +"pending" status. That works for very-long deliverables but hurts UX for +the common case (most podcasts finish in 10-30 seconds): the agent sends +a "kicked off, check back in a minute" reply *before* the worker is done, +so the user never gets a "ready" confirmation. + +This helper bridges that gap. The tool dispatches the Celery task as +before, then polls the artefact row's ``status`` column **until it +reaches a terminal value** (READY / FAILED). The tool then returns a +real terminal outcome — never a pending one. + +No wall-clock budget here on purpose +------------------------------------ +Layering a second budget on top of the existing per-invocation safety +nets just confused the UX. The real ceilings are: + +* **Multi-agent mode** — ``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` + (default ``300.0``, ``0`` to disable) caps how long any single + ``task(subagent, ...)`` invocation can run. If a deliverable needs + longer than this, the subagent invocation is cancelled and the + orchestrator surfaces a "subagent timed out" ToolMessage. Operators + who routinely generate long videos should raise that ceiling (or set + it to ``0`` for true unbounded waits). +* **Single-agent mode** — the chat's HTTP stream / process lifetime is + the only ceiling. Truly indefinite waits work here, but a dead Celery + worker will leave the row in PENDING/GENERATING forever; treat that + as an operational concern, not a UX concern. + +Configuration +------------- +None. The poll cadence is hardcoded at 1.5s — small enough to feel +responsive (~6 polls per typical 10s podcast), large enough to avoid +hammering the DB under burst traffic. Override at the call site if a +specific tool needs a different cadence. +""" + +from __future__ import annotations + +import asyncio +import logging +import time +from enum import Enum +from typing import Any + +from sqlalchemy import select +from sqlalchemy.orm import InstrumentedAttribute + +from app.db import shielded_async_session + +logger = logging.getLogger(__name__) + + +_DEFAULT_POLL_INTERVAL_SECONDS: float = 1.5 + + +async def wait_for_deliverable( + *, + model: type, + row_id: int, + columns: list[InstrumentedAttribute[Any]], + terminal_statuses: set[Enum], + poll_interval_s: float = _DEFAULT_POLL_INTERVAL_SECONDS, +) -> tuple[Enum, tuple[Any, ...], float]: + """Poll ``model`` row ``row_id`` until ``columns[0]`` reaches a terminal status. + + Blocks until the row's status column matches one of + ``terminal_statuses``. There is no internal wall-clock budget; cancel + from the outside (subagent timeout, HTTP disconnect, task + cancellation) if you need a ceiling. See module docstring. + + The first entry of ``columns`` must be the status column; additional + columns (e.g. ``Podcast.file_location``) are returned alongside the + final status so callers can build their payload without a second + roundtrip. + + A fresh ``shielded_async_session`` is opened per poll so we never + hold a transaction across the wait, and a failed poll is logged but + does not abort the wait — transient DB hiccups should not collapse + the tool call. + + Returns + ------- + ``(terminal_status, columns, elapsed_seconds)`` + ``columns`` mirrors the requested ``columns`` (including the + status itself in position 0). + """ + if not columns: + raise ValueError("wait_for_deliverable requires at least the status column") + + start = time.monotonic() + + while True: + await asyncio.sleep(poll_interval_s) + row: tuple[Any, ...] | None = None + try: + async with shielded_async_session() as session: + result = await session.execute( + select(*columns).where(model.id == row_id) + ) + row = result.first() + except Exception as exc: + logger.warning( + "[deliverable_wait] poll failed model=%s id=%s err=%r", + getattr(model, "__name__", str(model)), + row_id, + exc, + ) + + if row is not None: + status_val = row[0] + if status_val in terminal_statuses: + return status_val, tuple(row), time.monotonic() - start diff --git a/surfsense_backend/app/agents/shared/receipt.py b/surfsense_backend/app/agents/shared/receipt.py new file mode 100644 index 000000000..6f30067ee --- /dev/null +++ b/surfsense_backend/app/agents/shared/receipt.py @@ -0,0 +1,161 @@ +"""Receipt: structured handle returned by every mutating subagent tool. + +Generalises the Hermes ``entry`` dict (see ``references/hermes-agent/tools/ +delegate_tool.py:1663-1697``) for our 5 deliverable types + 15 connectors + +KB writes. The supervisor reads the Receipt to verify what actually happened +without round-tripping through LLM paraphrase. + +**Why this lives under ``app.agents.shared`` and not under either of the +two agent packages:** the Receipt is a *contract* shared between +``multi_agent_chat`` (where mutating tools emit it) and ``new_chat`` +(where ``filesystem_state.SurfSenseFilesystemState`` declares the +``receipts`` reducer that accumulates it, and where +``middleware.kb_persistence`` emits its own KB-write receipts). Putting +the contract in either package would create a bidirectional import +between the two — see the commit that introduced this module for the +``ImportError`` chain it broke. + +Each mutating tool wraps its native return shape into a Receipt via +:func:`make_receipt` (or builds one directly) and returns it under the +``"receipt"`` key alongside its existing payload. The subagent boundary +machinery in ``checkpointed_subagent_middleware.task_tool`` then folds +the receipt into the parent's ``receipts`` state via the append reducer. + +The KB write path is the one exception: file-tool calls cannot emit a +durable receipt because the actual DB writes happen end-of-turn inside +:class:`app.agents.new_chat.middleware.kb_persistence.KnowledgeBasePersistenceMiddleware`. +KB tools therefore emit a *provisional* receipt with ``status="pending"``; +the persistence middleware flips it to ``"success"`` or ``"failed"`` +before returning control to the parent. +""" + +from __future__ import annotations + +from typing import Any, Literal, TypedDict + +# Subagent that emitted this receipt. +ReceiptRoute = Literal[ + "deliverables", + "knowledge_base", + "notion", + "slack", + "gmail", + "linear", + "jira", + "clickup", + "confluence", + "calendar", + "luma", + "airtable", + "google_drive", + "dropbox", + "onedrive", + "discord", + "teams", +] + +# Within-route kind of artefact / external resource the operation touched. +# Left as ``str`` rather than a giant union so each route file documents +# its own enum next to its tools. +ReceiptType = str + +# Operation verb. Kept open for the same reason as ``ReceiptType``. +ReceiptOperation = str + +# Pending = async backend (Celery podcast / video) that the orchestrator +# will surface progress for out of band; persistence-MW flipped this to +# ``success`` for KB writes that committed. +ReceiptStatus = Literal["success", "pending", "failed"] + + +class Receipt(TypedDict, total=False): + """Structured per-mutation handle returned to the parent subagent. + + All fields are ``NotRequired`` (TypedDict ``total=False``) so each + route's tool can populate only the fields it actually has — e.g. Gmail + never sets ``verifiable_url`` because Gmail doesn't expose per-message + URLs. The receipts state reducer treats missing keys as missing rather + than ``null`` so we don't double-count. + """ + + route: ReceiptRoute + """Subagent name. Lets the orchestrator filter ``state['receipts']`` + by route without re-deriving from ``type``.""" + + type: ReceiptType + """Within-route kind. e.g. for ``deliverables`` one of ``{report, + podcast, video_presentation, resume, image}``; for ``notion`` ``page``; + for ``slack`` ``message``.""" + + operation: ReceiptOperation + """Verb. e.g. ``generate`` (deliverables), ``create`` / ``update`` / + ``delete`` (most connectors), ``send`` / ``post`` (chat), ``write_file`` + / ``edit_file`` / ``rm`` / ``rmdir`` / ``move_file`` / ``mkdir`` (KB).""" + + status: ReceiptStatus + """``success`` / ``pending`` / ``failed``. The verification teaching + in ``shared/snippets/verifiable_handle.md`` keys off this field.""" + + external_id: str | None + """Backend identifier. Report row id, Notion ``page_id``, Slack ``ts``, + Gmail ``message_id``, Linear identifier, KB ``virtualPath``, etc. + ``None`` only when the operation failed before the backend assigned one.""" + + verifiable_url: str | None + """URL the parent can pass to ``scrape_webpage`` to verify the + operation. ``None`` when no public URL exists (Gmail, KB, raw images + stored in the DB).""" + + preview: str | None + """Short snippet (~200 chars) of what was produced. First lines of + a generated report's markdown, transcript opener for a podcast, + thumbnail URL for an image. Lets the orchestrator decide whether to + re-render in the UI without re-loading the artefact.""" + + error: str | None + """Filled iff ``status == "failed"``. Plain-text reason; the parent + surfaces it in its own ``next_step``.""" + + +def make_receipt( + *, + route: ReceiptRoute, + type: str, + operation: str, + status: ReceiptStatus, + external_id: str | None = None, + verifiable_url: str | None = None, + preview: str | None = None, + error: str | None = None, +) -> Receipt: + """Construct a :class:`Receipt` with non-``None`` fields only. + + Drops keys whose value is ``None`` so downstream consumers can use + ``"verifiable_url" in receipt`` to distinguish "tool returned no URL" + from "tool deliberately surfaced ``null``". + """ + out: dict[str, Any] = { + "route": route, + "type": type, + "operation": operation, + "status": status, + } + if external_id is not None: + out["external_id"] = external_id + if verifiable_url is not None: + out["verifiable_url"] = verifiable_url + if preview is not None: + out["preview"] = preview + if error is not None: + out["error"] = error + return out # type: ignore[return-value] + + +__all__ = [ + "Receipt", + "ReceiptOperation", + "ReceiptRoute", + "ReceiptStatus", + "ReceiptType", + "make_receipt", +] diff --git a/surfsense_backend/app/agents/shared/receipt_command.py b/surfsense_backend/app/agents/shared/receipt_command.py new file mode 100644 index 000000000..f1c269e90 --- /dev/null +++ b/surfsense_backend/app/agents/shared/receipt_command.py @@ -0,0 +1,71 @@ +"""Helper for wrapping a tool result with a Receipt in a ``Command(update=...)``. + +Most mutating subagent tools historically returned a plain ``dict`` payload +which deepagents serialised straight into the ``ToolMessage`` content. To +participate in the verification teaching from +``multi_agent_chat/subagents/shared/snippets/verifiable_handle.md`` those +tools now also need to write a :class:`Receipt` into the parent's +``state['receipts']`` list (declared on +:class:`~app.agents.new_chat.filesystem_state.SurfSenseFilesystemState` +and backed by the append reducer). + +:func:`with_receipt` wraps both behaviours: it returns the tool payload as +a JSON-encoded ``ToolMessage`` AND appends the receipt to state in a single +:class:`~langgraph.types.Command`. Use it at every ``return`` site of a +mutating tool — including failure paths (emit a receipt with +``status="failed"`` and the error message in ``error``). +""" + +from __future__ import annotations + +import json +from typing import Any + +from langchain_core.messages import ToolMessage +from langgraph.types import Command + +from app.agents.shared.receipt import Receipt + + +def _content_to_text(payload: dict[str, Any] | str) -> str: + """Serialise a tool payload to ``ToolMessage`` content. + + Dicts go through ``json.dumps`` (matching deepagents' default tool-result + serialisation); strings are passed through. Anything else is coerced via + ``str`` so we never raise here — a mis-typed tool return would already + have failed inside the tool body. + """ + if isinstance(payload, str): + return payload + if isinstance(payload, dict): + return json.dumps(payload, default=str) + return str(payload) + + +def with_receipt( + *, + payload: dict[str, Any] | str, + receipt: Receipt, + tool_call_id: str, +) -> Command: + """Return a Command that ships ``payload`` as a ToolMessage AND appends ``receipt``. + + The append happens via the ``_list_append_reducer`` on the ``receipts`` + field of :class:`~app.agents.new_chat.filesystem_state.SurfSenseFilesystemState`, + so concurrent subagent batches (item 4 in the plan) won't clobber each + other's receipts. + """ + return Command( + update={ + "messages": [ + ToolMessage( + content=_content_to_text(payload), + tool_call_id=tool_call_id, + ) + ], + "receipts": [receipt], + } + ) + + +__all__ = ["with_receipt"] diff --git a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py index 496c6d0c3..a2f4d0bbd 100644 --- a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py +++ b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py @@ -62,7 +62,9 @@ class EtlPipelineService: return result if category == FileCategory.AUDIO: - content = await transcribe_audio(request.file_path, request.filename) + content = await transcribe_audio( + request.file_path, request.filename + ) result = EtlResult( markdown_content=content, etl_service="AUDIO", diff --git a/surfsense_backend/app/services/composio_service.py b/surfsense_backend/app/services/composio_service.py index d73a0d4ce..920f51d84 100644 --- a/surfsense_backend/app/services/composio_service.py +++ b/surfsense_backend/app/services/composio_service.py @@ -835,7 +835,14 @@ class ComposioService: ) if not result.get("success"): - return [], None, result.get("error", "Unknown error") + # 4-tuple to match this function's declared return shape + # ``(messages, next_page_token, result_size_estimate, error)``. + # The error branch previously dropped the + # ``result_size_estimate`` slot, which crashed the caller's + # unpack with ``ValueError: not enough values to unpack + # (expected 4, got 3)`` and hid the real Composio error + # (e.g. expired connected account / invalid API key). + return [], None, None, result.get("error", "Unknown error") data = result.get("data", {}) diff --git a/surfsense_backend/app/services/gmail/kb_sync_service.py b/surfsense_backend/app/services/gmail/kb_sync_service.py index 6ff5f3c2b..85e25fcb6 100644 --- a/surfsense_backend/app/services/gmail/kb_sync_service.py +++ b/surfsense_backend/app/services/gmail/kb_sync_service.py @@ -101,9 +101,7 @@ class GmailKBSyncService: else: logger.warning("No LLM configured -- using fallback summary") summary_content = f"Gmail Message: {subject}\n\n{indexable_content}" - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/surfsense_backend/app/services/google_calendar/kb_sync_service.py b/surfsense_backend/app/services/google_calendar/kb_sync_service.py index 1f017ec4d..e59868aff 100644 --- a/surfsense_backend/app/services/google_calendar/kb_sync_service.py +++ b/surfsense_backend/app/services/google_calendar/kb_sync_service.py @@ -116,9 +116,7 @@ class GoogleCalendarKBSyncService: summary_content = ( f"Google Calendar Event: {event_summary}\n\n{indexable_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -297,9 +295,7 @@ class GoogleCalendarKBSyncService: summary_content = ( f"Google Calendar Event: {event_summary}\n\n{indexable_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/surfsense_backend/app/services/jira/kb_sync_service.py b/surfsense_backend/app/services/jira/kb_sync_service.py index 5f6668377..37001a476 100644 --- a/surfsense_backend/app/services/jira/kb_sync_service.py +++ b/surfsense_backend/app/services/jira/kb_sync_service.py @@ -98,9 +98,7 @@ class JiraKBSyncService: summary_content = ( f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(issue_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -214,9 +212,7 @@ class JiraKBSyncService: summary_content = ( f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(issue_content) diff --git a/surfsense_backend/app/services/llm_service.py b/surfsense_backend/app/services/llm_service.py index fa97fb33a..aadb60cde 100644 --- a/surfsense_backend/app/services/llm_service.py +++ b/surfsense_backend/app/services/llm_service.py @@ -682,11 +682,7 @@ def get_planner_llm() -> ChatLiteLLM | None: from app.agents.new_chat.llm_config import create_chat_litellm_from_config planner_cfg = next( - ( - cfg - for cfg in config.GLOBAL_LLM_CONFIGS - if cfg.get("is_planner") is True - ), + (cfg for cfg in config.GLOBAL_LLM_CONFIGS if cfg.get("is_planner") is True), None, ) if not planner_cfg: diff --git a/surfsense_backend/app/services/onedrive/kb_sync_service.py b/surfsense_backend/app/services/onedrive/kb_sync_service.py index e1da3b4a1..731f081dd 100644 --- a/surfsense_backend/app/services/onedrive/kb_sync_service.py +++ b/surfsense_backend/app/services/onedrive/kb_sync_service.py @@ -96,9 +96,7 @@ class OneDriveKBSyncService: else: logger.warning("No LLM configured — using fallback summary") summary_content = f"OneDrive File: {file_name}\n\n{indexable_content}" - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index 1b2a4cfbb..78f80c955 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -2608,9 +2608,7 @@ async def stream_resume_chat( visibility = thread_visibility or ChatVisibility.PRIVATE from app.config import config as _app_config - chat_agent_mode = ( - "multi" if _app_config.MULTI_AGENT_CHAT_ENABLED else "single" - ) + chat_agent_mode = "multi" if _app_config.MULTI_AGENT_CHAT_ENABLED else "single" with contextlib.suppress(Exception): chat_span.set_attribute("agent.mode", chat_agent_mode) _t0 = time.perf_counter() diff --git a/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py b/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py index ad4a17d08..2ff810447 100644 --- a/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py +++ b/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py @@ -6,6 +6,9 @@ import json from collections.abc import Iterator from typing import Any +from langchain_core.messages import ToolMessage +from langgraph.types import Command + from app.tasks.chat.streaming.handlers.tools import ( ToolCompletionEmissionContext, iter_tool_completion_emission_frames, @@ -19,6 +22,38 @@ from app.tasks.chat.streaming.relay.task_span import ( from app.tasks.chat.streaming.relay.thinking_step_sse import emit_thinking_step_frame +def _unwrap_command_output(raw_output: Any) -> Any: + """Replace a ``Command`` from a tool return with its inner ``ToolMessage``. + + Tools that participate in receipt-style state writes (see + ``app.agents.shared.receipt_command.with_receipt``) return a + ``Command(update={"messages": [ToolMessage(...)], "receipts": [...]})``. + LangChain's ``on_tool_end`` event surfaces that ``Command`` verbatim as + ``data.output``, which the rest of this handler can't introspect: it has + no ``.content``, isn't a ``dict``, and stringifies to ``"Command(...)"``. + That stringified payload reaches the frontend and breaks tool-specific + UI components (e.g. the podcast card) that look for ``status`` / + ``podcast_id`` at the top level. + + We extract the first ``ToolMessage`` from the Command's ``messages`` list + so downstream code can read ``.content`` normally. Commands that don't + contain a ``ToolMessage`` (rare, e.g. pure state updates) are returned + unchanged — the existing ``str(raw_output)`` fallback handles them. + """ + if not isinstance(raw_output, Command): + return raw_output + update = raw_output.update + if not isinstance(update, dict): + return raw_output + messages = update.get("messages") + if not isinstance(messages, list): + return raw_output + for msg in messages: + if isinstance(msg, ToolMessage): + return msg + return raw_output + + def iter_tool_end_frames( event: dict[str, Any], *, @@ -33,7 +68,7 @@ def iter_tool_end_frames( state.active_tool_depth = max(0, state.active_tool_depth - 1) run_id = event.get("run_id", "") tool_name = event.get("name", "unknown_tool") - raw_output = event.get("data", {}).get("output", "") + raw_output = _unwrap_command_output(event.get("data", {}).get("output", "")) staged_file_path = state.file_path_by_run.pop(run_id, None) if run_id else None if hasattr(raw_output, "content"): diff --git a/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py b/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py index 21e27d4c3..51a67f369 100644 --- a/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py +++ b/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py @@ -15,12 +15,24 @@ def iter_completion_emission_frames( out = ctx.tool_output payload = out if isinstance(out, dict) else {"result": out} yield ctx.emit_tool_output_card(payload) - if isinstance(out, dict) and out.get("status") == "pending": + if not isinstance(out, dict): + return + status = out.get("status") + # ``ready`` is the live success status now that the tool waits for the + # Celery worker to reach a terminal state. ``pending`` is retained as a + # legacy branch for old saved chats that pre-date the wait-for-terminal + # change (see ``app.agents.shared.deliverable_wait``). + if status == "ready": + yield ctx.streaming_service.format_terminal_info( + f"Video presentation generated successfully: {out.get('title', 'Presentation')}", + "success", + ) + elif status == "pending": yield ctx.streaming_service.format_terminal_info( f"Video presentation queued: {out.get('title', 'Presentation')}", "success", ) - elif isinstance(out, dict) and out.get("status") == "failed": + elif status == "failed": error_msg = out.get("error", "Unknown error") yield ctx.streaming_service.format_terminal_info( f"Presentation generation failed: {error_msg}", diff --git a/surfsense_backend/app/utils/document_converters.py b/surfsense_backend/app/utils/document_converters.py index 9bc8103c5..059d91806 100644 --- a/surfsense_backend/app/utils/document_converters.py +++ b/surfsense_backend/app/utils/document_converters.py @@ -222,9 +222,7 @@ async def generate_document_summary( else: enhanced_summary_content = summary_content - summary_embedding = await asyncio.to_thread( - embed_text, enhanced_summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, enhanced_summary_content) return enhanced_summary_content, summary_embedding From 2b7d91aa0323a757b169294302c3ab12a4b39881 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:12:02 +0200 Subject: [PATCH 065/133] =?UTF-8?q?feat(automations):=20add=20create=5Faut?= =?UTF-8?q?omation=20HITL=20tool=20(NL=20=E2=86=92=20draft=20=E2=86=92=20a?= =?UTF-8?q?pprove=20=E2=86=92=20save)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single tool exposed to the main agent. The main agent passes a natural-language `intent`; a focused drafter sub-LLM turns it into a full AutomationCreate JSON; that JSON is surfaced via request_approval (action_type "automation_create") so the user can edit/approve it on a frontend card; on approval the tool persists via AutomationService. Three phases, one tool call. Scope split: - main agent sees only `intent: str` (no schema knowledge leaks into the calling graph) — prompt fragments scoped accordingly. - drafter sub-LLM owns the schema + few-shot intent→JSON examples — lives in the generating graph's prompt (tools/automation/prompt.py). Files: - main_agent/tools/automation/{create.py, prompt.py, __init__.py}: new tool + drafter system prompt with two few-shot intent→JSON examples. - system_prompt/prompts/tools/create_automation/{description.md, example.md}: intent-only guidance for the main agent. - main_agent/tools/index.py: add create_automation to the main-agent allowlist. - new_chat/tools/registry.py: deferred-import factory to break the multi_agent_chat ↔ registry cycle; one ToolDefinition entry. --- .../tools/create_automation/__init__.py | 1 + .../tools/create_automation/description.md | 31 +++ .../tools/create_automation/example.md | 13 ++ .../main_agent/tools/automation/__init__.py | 7 + .../main_agent/tools/automation/create.py | 203 ++++++++++++++++++ .../main_agent/tools/automation/prompt.py | 179 +++++++++++++++ .../main_agent/tools/index.py | 1 + .../app/agents/new_chat/tools/registry.py | 37 ++++ 8 files changed, 472 insertions(+) create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py new file mode 100644 index 000000000..30699a4a1 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py @@ -0,0 +1 @@ +"""``create_automation`` — description + few-shot examples.""" diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md new file mode 100644 index 000000000..25b4eec47 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md @@ -0,0 +1,31 @@ +- `create_automation` — Draft and author a new automation. You describe the + user's intent; a focused drafter inside the tool turns it into the full + automation JSON; the user reviews and edits it on an approval card; on + approval it's saved. All three phases happen in a single tool call. + - Call when the user wants SurfSense to do something on its own: anything + recurring or scheduled ("every morning…", "each Monday…", "weekly + recap…"). + - Args: + - `intent` (string): restate the user's request **concretely**, in one + paragraph. Cover three things: + - **What** should run (the action: summarize, recap, post, draft, …). + - **When** it should run (schedule + timezone if the user mentioned one; + otherwise leave the timezone for the drafter to default to UTC). + - **Static values** the automation needs (folder ids, channel names, + project keys, parent page ids, …) — list them with their values. + If the user did NOT supply one the automation needs, say so + explicitly ("the Notion parent page id was not specified") so the + drafter leaves a placeholder. + - Do NOT prompt the user to confirm before calling — the approval card + IS the confirmation. The user can edit any field on the card. + - Returns: + - `{status: "saved", automation_id, name}` — confirm briefly to the + user ("Saved as automation #N — runs <when>."). Don't dump JSON back. + - `{status: "rejected", message}` — the user declined on the card. + Acknowledge once ("Understood, I didn't create it.") and stop. Do + NOT retry or pitch variants. + - `{status: "invalid", issues, raw?}` — drafting/validation failed + before the card was shown. Read the issues, refine your `intent` + with the missing details, call again. + - `{status: "error", message}` — surface the message verbatim and + offer to retry. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md new file mode 100644 index 000000000..19311bef0 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md @@ -0,0 +1,13 @@ +<example> +user: "Every weekday at 9am, summarize new documents in folder 12 and post the summary to Slack channel #daily-digest." +→ create_automation(intent="Every weekday at 09:00 UTC, summarize documents added to folder_id=12 since the last run, then post the summary to Slack channel '#daily-digest'. Static inputs: folder_id=12, slack_channel='#daily-digest'.") +tool returns: {"status": "saved", "automation_id": 42, "name": "Daily folder 12 digest"} +(Reply briefly: "Saved as automation #42 — runs weekdays at 9am UTC.") +</example> + +<example> +user: "Once a week on Mondays at 7am Paris time, draft a Notion page recapping last week's Jira tickets in project CORE." +→ create_automation(intent="Every Monday at 07:00 Europe/Paris, read last week's Jira issues in project CORE, then draft a Notion page recapping them. Static inputs: jira_project_key='CORE'. The user did NOT specify which Notion page the recap should sit under — leave notion_parent_page_id as a placeholder.") +tool returns: {"status": "saved", "automation_id": 51, "name": "Weekly CORE Jira recap"} +(Reply: "Saved as automation #51. I left the Notion parent page id as a placeholder — set it on the automation before next Monday.") +</example> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py new file mode 100644 index 000000000..d47bbac7e --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py @@ -0,0 +1,7 @@ +"""``create_automation`` — author + persist an automation via a HITL card.""" + +from __future__ import annotations + +from .create import create_create_automation_tool + +__all__ = ["create_create_automation_tool"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py new file mode 100644 index 000000000..78fedde22 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -0,0 +1,203 @@ +"""``create_automation`` — NL intent → drafted JSON → HITL approval card → persisted. + +Single tool that: + +1. Drafts a structured automation from the user's intent via a focused sub-LLM + (system prompt in :mod:`.prompt`). +2. Surfaces the validated draft in a HITL approval card + (``action_type="automation_create"``). +3. On approval, validates the (possibly edited) payload again and persists + it via :class:`AutomationService`. + +The main agent only restates the user's request as a single ``intent`` string. +The drafting sub-LLM owns the JSON shape; the HITL card is the user's review. +""" + +from __future__ import annotations + +import json +import logging +import re +from typing import Any +from uuid import UUID + +from fastapi import HTTPException +from langchain_core.messages import HumanMessage +from langchain_core.tools import tool +from pydantic import ValidationError + +from app.agents.new_chat.tools.hitl import request_approval +from app.automations.schemas.api import AutomationCreate +from app.automations.services.automation import AutomationService +from app.db import User, async_session_maker +from app.utils.content_utils import extract_text_content + +from .prompt import build_draft_prompt + +logger = logging.getLogger(__name__) + +_JSON_FENCE = re.compile(r"```(?:json)?\s*(.*?)\s*```", re.DOTALL) + + +def create_create_automation_tool( + *, + search_space_id: int, + user_id: str | UUID, + llm: Any, +): + """Factory for the ``create_automation`` tool. + + ``search_space_id`` is injected from the chat session (the model never + has to guess it). ``llm`` is the drafting sub-model — we reuse the main + agent's LLM and tag the call so it's identifiable in traces. A fresh + ``AsyncSession`` is opened per call to avoid stale sessions on + compiled-agent cache hits (same pattern as the Notion / memory tools). + """ + uid = UUID(user_id) if isinstance(user_id, str) else user_id + + @tool + async def create_automation(intent: str) -> dict[str, Any]: + """Draft + save an automation from a natural-language intent. + + Use this when the user wants SurfSense to do something on its own + on a schedule (e.g. "every morning summarize folder 12 to Slack"). + Restate the user's request as ONE concrete ``intent`` string: what + should run, when, and which static values (folder ids, channel + names, …) it needs. + + The tool drafts the full automation JSON internally, shows the user + an approval card for review, and persists on approval. Do NOT + prompt the user to confirm before calling — the card IS the + confirmation. The user can edit any field there. + + Args: + intent: Concrete restatement of the user's request. Include + the schedule (with timezone if mentioned), the action to + take, and any static values. Example: "Every weekday at + 09:00 UTC, summarize new docs added to folder_id=12 since + the last run, then post the summary to Slack channel + '#daily-digest'." + + Returns: + ``{"status": "saved", "automation_id": int, "name": str}`` on + approval + save. + ``{"status": "rejected", "message": "..."}`` when the user + declines on the card. + ``{"status": "invalid", "issues": [...], "raw": ...}`` when + the drafter produced output that did not validate (call again + with a more precise intent). + ``{"status": "error", "message": "..."}`` on drafter or + persistence failure. + + IMPORTANT: when status is ``"rejected"`` the user explicitly + declined. Acknowledge once and stop — do NOT retry or pitch + variants without a fresh user request. + """ + # --- 1. Draft via sub-LLM --- + prompt = build_draft_prompt(search_space_id=search_space_id, intent=intent) + try: + response = await llm.ainvoke( + [HumanMessage(content=prompt)], + config={"tags": ["surfsense:internal", "automation-draft"]}, + ) + except Exception as exc: + logger.exception("create_automation drafting LLM call failed") + return {"status": "error", "message": f"drafting failed: {exc}"} + + raw_text = extract_text_content(response.content).strip() + draft = _extract_json(raw_text) + if draft is None: + return { + "status": "invalid", + "issues": ["model output was not parseable JSON"], + "raw": raw_text, + } + + # search_space_id is injected here so the sub-LLM never has to guess. + draft["search_space_id"] = search_space_id + try: + validated_draft = AutomationCreate.model_validate(draft) + except ValidationError as exc: + return { + "status": "invalid", + "issues": _format_validation_issues(exc), + "raw": draft, + } + + # --- 2. HITL approval card --- + try: + card_params = validated_draft.model_dump(mode="json", by_alias=True) + # search_space_id is session-scoped, not user-editable. + card_params.pop("search_space_id", None) + + result = request_approval( + action_type="automation_create", + tool_name="create_automation", + params=card_params, + context={"search_space_id": search_space_id}, + ) + + if result.rejected: + return { + "status": "rejected", + "message": "User declined. Do not retry or suggest alternatives.", + } + + # --- 3. Persist (re-validate in case the user edited) --- + final_payload = {**result.params, "search_space_id": search_space_id} + try: + final_validated = AutomationCreate.model_validate(final_payload) + except ValidationError as exc: + return { + "status": "invalid", + "issues": _format_validation_issues(exc), + } + + async with async_session_maker() as session: + user = await session.get(User, uid) + if user is None: + return { + "status": "error", + "message": "user not found in this session", + } + service = AutomationService(session=session, user=user) + created = await service.create(final_validated) + return { + "status": "saved", + "automation_id": created.id, + "name": created.name, + } + + except HTTPException as exc: + return {"status": "error", "message": exc.detail} + except Exception as exc: + from langgraph.errors import GraphInterrupt + + if isinstance(exc, GraphInterrupt): + raise + logger.exception("create_automation failed") + return {"status": "error", "message": f"persistence failed: {exc}"} + + return create_automation + + +def _extract_json(text: str) -> dict[str, Any] | None: + """Pull a JSON object out of the model response, tolerating ``` fences.""" + if not text: + return None + candidate = text + fence_match = _JSON_FENCE.search(text) + if fence_match: + candidate = fence_match.group(1) + try: + parsed = json.loads(candidate) + except json.JSONDecodeError: + return None + return parsed if isinstance(parsed, dict) else None + + +def _format_validation_issues(exc: ValidationError) -> list[str]: + return [ + f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}" + for err in exc.errors() + ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py new file mode 100644 index 000000000..45870e768 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py @@ -0,0 +1,179 @@ +"""System prompt for the drafting sub-LLM inside ``create_automation``. + +Converts a natural-language ``intent`` into a structured ``AutomationCreate`` +JSON object. That object becomes the payload the HITL approval card surfaces. + +Scope split: + Real automation JSONs live here — this is the graph that *generates* + the JSON. The main agent's prompt fragments (``description.md`` / + ``example.md``) only carry intent-string examples; the main agent + never sees the schema. + +Layout: + The prompt is concatenated from four format-safe pieces. ``_HEADER`` / + ``_FOOTER`` carry the only ``str.format`` placeholders; ``_SCHEMA`` and + ``_FEW_SHOTS`` are plain strings so their JSON literals (and the + ``{{ inputs.X }}`` Jinja references in queries) can stay readable + without doubled-brace escaping. + +Catalog handling: + v1 hard-codes the action/trigger catalog (one action, one trigger). + When new types ship, swap the inline lines for a render-time pull + from ``app.automations.actions`` / ``app.automations.triggers`` via + lazy imports inside :func:`build_draft_prompt` so this module never + participates in the ``multi_agent_chat`` import cycle. +""" + +from __future__ import annotations + +from datetime import UTC, datetime + + +_HEADER = """\ +You are the SurfSense automation drafter. Convert the user intent below +into a SINGLE JSON object matching the AutomationCreate schema. Output +ONLY that JSON object — no prose, no markdown fence, no commentary. + +Current UTC time (for cron context): {now} +Target search_space_id: {search_space_id} +""" + + +_SCHEMA = """ +Required JSON shape: +{ + "name": "<1-200 char identifier>", + "description": "<one-liner or null>", + "definition": { + "schema_version": "1.0", + "name": "<same as outer name>", + "goal": "<one sentence>", + "plan": [ + { + "step_id": "<slug>", + "action": "agent_task", + "params": { + "query": "<Jinja string referencing {{ inputs.X }}>", + "auto_approve_all": true + } + } + ], + "metadata": {"tags": ["..."]} + }, + "triggers": [ + { + "type": "schedule", + "params": {"cron": "<5-field cron>", "timezone": "<IANA tz, default UTC>"}, + "static_inputs": {"<key>": <value>, ...}, + "enabled": true + } + ] +} + +v1 catalog (only these are valid): +- Actions: agent_task — params: query (string, Jinja), auto_approve_all (bool). +- Triggers: schedule — params: cron (5-field), timezone (IANA, e.g. "UTC", + "Europe/Paris"). Has static_inputs (object). + +Conventions: +- Whatever the plan references via {{ inputs.X }} MUST appear either in a + trigger's static_inputs OR in definition.inputs.schema_.properties so the + executor can resolve it at fire time. +- static_inputs carries values that stay the same across every fire + (folder ids, channel names, project keys, parent page ids). Put them on + the trigger that supplies them, not in the plan. +- If the user did NOT supply a value the plan needs, put "REPLACE_ME" in + static_inputs. Do NOT invent ids, channels, or paths. +- Cron is 5-field (minute hour day-of-month month day-of-week). Use the + timezone the user mentioned; default "UTC" when unspecified. +- Templating variables available at fire time: inputs.* (merged + static_inputs + runtime), inputs.fired_at, inputs.last_fired_at. +""" + + +_FEW_SHOTS = """ +Few-shot examples (intent → JSON output): + +### Example 1 — schedule with all static values supplied +intent: "Every weekday at 09:00 UTC, summarize documents added to folder_id=12 since the last run, then post the summary to Slack channel '#daily-digest'. Static inputs: folder_id=12, slack_channel='#daily-digest'." +output: +{ + "name": "Daily folder 12 digest", + "description": "Weekday 09:00 UTC summary of folder 12 documents posted to #daily-digest", + "definition": { + "schema_version": "1.0", + "name": "Daily folder 12 digest", + "goal": "Summarize new docs in folder 12 since the last run and post to #daily-digest", + "plan": [ + { + "step_id": "summarize_and_post", + "action": "agent_task", + "params": { + "query": "Summarize documents added to folder {{ inputs.folder_id }} since {{ inputs.last_fired_at or 'yesterday' }}, then send the summary to Slack channel {{ inputs.slack_channel }}.", + "auto_approve_all": true + } + } + ], + "metadata": {"tags": ["daily", "digest", "slack"]} + }, + "triggers": [ + { + "type": "schedule", + "params": {"cron": "0 9 * * 1-5", "timezone": "UTC"}, + "static_inputs": {"folder_id": 12, "slack_channel": "#daily-digest"}, + "enabled": true + } + ] +} + +### Example 2 — schedule with a missing value (REPLACE_ME placeholder) +intent: "Every Monday at 07:00 Europe/Paris, read last week's Jira issues in project CORE, then draft a Notion page recapping them. Static inputs: jira_project_key='CORE'. The user did NOT specify the Notion parent page id — leave it as a placeholder." +output: +{ + "name": "Weekly CORE Jira recap", + "description": "Monday 07:00 Europe/Paris recap of last week's CORE Jira issues, drafted to Notion", + "definition": { + "schema_version": "1.0", + "name": "Weekly CORE Jira recap", + "goal": "Recap last week's CORE Jira issues into a Notion page", + "plan": [ + { + "step_id": "recap", + "action": "agent_task", + "params": { + "query": "List Jira issues in project {{ inputs.jira_project_key }} updated in the 7 days before {{ inputs.fired_at }}. Draft a Notion page under parent id {{ inputs.notion_parent_page_id }} titled 'CORE recap — week of {{ inputs.fired_at }}'.", + "auto_approve_all": true + } + } + ], + "metadata": {"tags": ["weekly", "recap", "jira", "notion"]} + }, + "triggers": [ + { + "type": "schedule", + "params": {"cron": "0 7 * * 1", "timezone": "Europe/Paris"}, + "static_inputs": {"jira_project_key": "CORE", "notion_parent_page_id": "REPLACE_ME"}, + "enabled": true + } + ] +} +""" + + +_FOOTER = """ +User intent: +{intent} +""" + + +def build_draft_prompt(*, search_space_id: int, intent: str) -> str: + """Render the drafting sub-LLM system prompt for the given intent.""" + return ( + _HEADER.format( + now=datetime.now(UTC).isoformat(timespec="seconds"), + search_space_id=search_space_id, + ) + + _SCHEMA + + _FEW_SHOTS + + _FOOTER.format(intent=intent.strip()) + ) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py index 5d309261c..88509eda7 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py @@ -10,6 +10,7 @@ MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED: tuple[str, ...] = ( "web_search", "scrape_webpage", "update_memory", + "create_automation", ) MAIN_AGENT_SURFSENSE_TOOL_NAMES: frozenset[str] = frozenset( diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index b842d7a20..8c263ca20 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -150,6 +150,28 @@ class ToolDefinition: reverse: Callable[[dict[str, Any], Any], dict[str, Any]] | None = None +# ============================================================================= +# Deferred-import factories +# ============================================================================= +# Used for tools whose impls live under ``multi_agent_chat``. Importing those +# at module-load time would cycle (``multi_agent_chat`` middleware imports +# this registry). The import inside the factory runs only when +# ``build_tools`` is called, by which point ``multi_agent_chat`` is fully +# initialised. + + +def _build_create_automation_tool(deps: dict[str, Any]) -> BaseTool: + from app.agents.multi_agent_chat.main_agent.tools.automation import ( + create_create_automation_tool, + ) + + return create_create_automation_tool( + search_space_id=deps["search_space_id"], + user_id=deps["user_id"], + llm=deps["llm"], + ) + + # ============================================================================= # Built-in Tools Registry # ============================================================================= @@ -261,6 +283,21 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ requires=["db_session", "search_space_id", "user_id"], ), # ========================================================================= + # AUTOMATION AUTHORING - single HITL tool. The tool takes an NL ``intent`` + # from the main agent, drafts the full AutomationCreate JSON via a focused + # sub-LLM, surfaces it on an approval card, and persists on approval. The + # factory defers its import because the impl lives under ``multi_agent_chat`` + # and that package transitively pulls this registry via middleware; + # deferring to ``build_tools`` call-time breaks the cycle without a + # parallel registry. + # ========================================================================= + ToolDefinition( + name="create_automation", + description="Draft an automation from an NL intent; user approves the card; tool saves", + factory=_build_create_automation_tool, + requires=["search_space_id", "user_id", "llm"], + ), + # ========================================================================= # MEMORY TOOL - single update_memory, private or team by thread_visibility # ========================================================================= ToolDefinition( From 79f0218360e713ad552d2d8138cd744efc1687ca Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:30:40 +0200 Subject: [PATCH 066/133] rbac: surface automations permissions in the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend already defined automations:create/read/update/delete/execute and seeded them on Owner/Editor/Viewer roles, but the Settings → Roles UI was missing the metadata to render them properly. - backend: add PERMISSION_DESCRIPTIONS entries for the 5 automations perms so the role editor stops falling back to "Permission for automations:create". - frontend: add automations to CATEGORY_CONFIG (Workflow icon, slotted between podcasts and connectors) so the role editor groups them as a real section. - frontend: extend the three ROLE_PRESETS — Editor and Contributor get create/read/update/execute (mirroring backend Editor); Viewer gets read. Prep work for the automations frontend; canPerform/usePermissionGate already handle the runtime gating, so no new hook is needed. --- surfsense_backend/app/routes/rbac_routes.py | 6 ++++++ .../components/settings/roles-manager.tsx | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/surfsense_backend/app/routes/rbac_routes.py b/surfsense_backend/app/routes/rbac_routes.py index 38ae31269..3b91e456d 100644 --- a/surfsense_backend/app/routes/rbac_routes.py +++ b/surfsense_backend/app/routes/rbac_routes.py @@ -107,6 +107,12 @@ PERMISSION_DESCRIPTIONS = { "settings:view": "View search space settings", "settings:update": "Modify search space settings", "settings:delete": "Delete the entire search space", + # Automations + "automations:create": "Create automations from chat or JSON", + "automations:read": "View automations, their triggers, and run history", + "automations:update": "Edit automations and manage their triggers", + "automations:delete": "Remove automations from the search space", + "automations:execute": "Manually fire automations", # Full access "*": "Full access to all features and settings", } diff --git a/surfsense_web/components/settings/roles-manager.tsx b/surfsense_web/components/settings/roles-manager.tsx index 88595e748..5c034470d 100644 --- a/surfsense_web/components/settings/roles-manager.tsx +++ b/surfsense_web/components/settings/roles-manager.tsx @@ -23,6 +23,7 @@ import { Unplug, Users, Video, + Workflow, } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -126,6 +127,12 @@ const CATEGORY_CONFIG: Record< description: "Generate AI podcasts from content", order: 5, }, + automations: { + label: "Automations", + icon: Workflow, + description: "Scheduled and event-driven agent tasks", + order: 5.5, + }, connectors: { label: "Connectors", icon: Unplug, @@ -200,6 +207,10 @@ const ROLE_PRESETS = { "podcasts:create", "podcasts:read", "podcasts:update", + "automations:create", + "automations:read", + "automations:update", + "automations:execute", "connectors:create", "connectors:read", "connectors:update", @@ -220,6 +231,7 @@ const ROLE_PRESETS = { "comments:read", "llm_configs:read", "podcasts:read", + "automations:read", "connectors:read", "logs:read", "members:view", @@ -240,6 +252,10 @@ const ROLE_PRESETS = { "comments:read", "llm_configs:read", "podcasts:read", + "automations:create", + "automations:read", + "automations:update", + "automations:execute", "connectors:read", "logs:read", "members:view", From d48bb2033be03f4a86d12ebff79468c1f1913314 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:55:46 +0200 Subject: [PATCH 067/133] fix(web): handle 204 No Content responses in base API service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DELETE endpoints in the automations API return 204; calling .json() on an empty body throws SyntaxError. Treat 204 as data=null and skip schema validation so callers can opt out of response bodies without errors or spurious schema-mismatch warnings. Also drops a pre-existing 'unknown → BodyInit' type error on the non-JSON body branch via a narrow cast (caller is responsible for passing a real BodyInit when Content-Type isn't application/json). --- surfsense_web/lib/apis/base-api.service.ts | 57 +++++++++++++--------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts index 0819cbc7c..a0039b63a 100644 --- a/surfsense_web/lib/apis/base-api.service.ts +++ b/surfsense_web/lib/apis/base-api.service.ts @@ -1,4 +1,5 @@ import type { ZodType } from "zod"; +import { BACKEND_URL } from "@/lib/env-config"; import { getClientPlatform } from "../agent-filesystem"; import { getBearerToken, handleUnauthorized, refreshAccessToken } from "../auth-utils"; import { @@ -9,7 +10,7 @@ import { NetworkError, NotFoundError, } from "../error"; -import { BACKEND_URL } from "@/lib/env-config"; + enum ResponseType { JSON = "json", TEXT = "text", @@ -122,8 +123,9 @@ class BaseApiService { if (contentType === "application/json" && typeof mergedOptions.body === "object") { fetchOptions.body = JSON.stringify(mergedOptions.body); } else { - // Pass body as-is for other content types (e.g., form data, already stringified) - fetchOptions.body = mergedOptions.body; + // Pass body as-is for other content types (form data, already stringified). + // Caller is responsible for passing a real BodyInit when Content-Type is not JSON. + fetchOptions.body = mergedOptions.body as BodyInit; } } @@ -210,32 +212,39 @@ class BaseApiService { let data; const responseType = mergedOptions.responseType; - try { - switch (responseType) { - case ResponseType.JSON: - data = await response.json(); - break; - case ResponseType.TEXT: - data = await response.text(); - break; - case ResponseType.BLOB: - data = await response.blob(); - break; - case ResponseType.ARRAY_BUFFER: - data = await response.arrayBuffer(); - break; - // Add more cases as needed - default: - data = await response.json(); + if (response.status === 204) { + // 204 No Content has no body; .json() would throw SyntaxError. + // Leave data as null and skip schema validation below so endpoints + // that opt out of bodies (REST-style DELETE) don't error on success. + data = null; + } else { + try { + switch (responseType) { + case ResponseType.JSON: + data = await response.json(); + break; + case ResponseType.TEXT: + data = await response.text(); + break; + case ResponseType.BLOB: + data = await response.blob(); + break; + case ResponseType.ARRAY_BUFFER: + data = await response.arrayBuffer(); + break; + // Add more cases as needed + default: + data = await response.json(); + } + } catch (error) { + console.error("Failed to parse response as JSON:", error); + throw new AppError("Failed to parse response", response.status, response.statusText); } - } catch (error) { - console.error("Failed to parse response as JSON:", error); - throw new AppError("Failed to parse response", response.status, response.statusText); } // Validate response if (responseType === ResponseType.JSON) { - if (!responseSchema) { + if (!responseSchema || response.status === 204) { return data; } const parsedData = responseSchema.safeParse(data); From b18a5fdca92ba7fcfd9e3240747e47dc2adedbf0 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:55:57 +0200 Subject: [PATCH 068/133] feat(web): automations contracts, API client, atoms and hooks Foundation for the v1 automations UI. Mirrors backend Pydantic schemas into Zod and wires the data layer end-to-end so feature surfaces can be built on top. contracts/types/automation.types.ts: - AutomationStatus, TriggerType, RunStatus enums. - AutomationDefinition envelope (PlanStep, TriggerSpec, Execution, Metadata, Inputs). - AutomationCreate/Update/Detail/Summary/List + listParams. - TriggerCreate/Update/Detail. - RunSummary/Detail/List + runListParams. lib/apis/automations-api.service.ts: - list/get/create/update/delete automations. - add/update/remove triggers (sub-resource). - list/get runs (read-only sub-resource). - safeParse on every write, 204-safe deletes. atoms/automations/: - automationsListAtom (active search space, first page). - 6 mutation atoms with toast + cache invalidation. hooks/: - use-automations.ts wraps the list atom. - use-automation.ts: parameterized detail by id. - use-automation-runs.ts: useAutomationRuns + useAutomationRun. lib/query-client/cache-keys.ts: automations namespace (list, detail, runs, run) keyed by (id, limit, offset) where relevant. Smoke: zod round-trip OK on backend-shape payloads (Automation, AutomationCreate, Trigger, Run); typecheck clean for new files; biome clean. --- .../automations/automations-mutation.atoms.ts | 127 ++++++++++++ .../automations/automations-query.atoms.ts | 31 +++ .../contracts/types/automation.types.ts | 193 ++++++++++++++++++ surfsense_web/hooks/use-automation-runs.ts | 42 ++++ surfsense_web/hooks/use-automation.ts | 19 ++ surfsense_web/hooks/use-automations.ts | 24 +++ .../lib/apis/automations-api.service.ts | 102 +++++++++ surfsense_web/lib/query-client/cache-keys.ts | 10 + 8 files changed, 548 insertions(+) create mode 100644 surfsense_web/atoms/automations/automations-mutation.atoms.ts create mode 100644 surfsense_web/atoms/automations/automations-query.atoms.ts create mode 100644 surfsense_web/contracts/types/automation.types.ts create mode 100644 surfsense_web/hooks/use-automation-runs.ts create mode 100644 surfsense_web/hooks/use-automation.ts create mode 100644 surfsense_web/hooks/use-automations.ts create mode 100644 surfsense_web/lib/apis/automations-api.service.ts diff --git a/surfsense_web/atoms/automations/automations-mutation.atoms.ts b/surfsense_web/atoms/automations/automations-mutation.atoms.ts new file mode 100644 index 000000000..f5e4fd5f4 --- /dev/null +++ b/surfsense_web/atoms/automations/automations-mutation.atoms.ts @@ -0,0 +1,127 @@ +import { atomWithMutation } from "jotai-tanstack-query"; +import { toast } from "sonner"; +import type { + AutomationCreateRequest, + AutomationUpdateRequest, + TriggerCreateRequest, + TriggerUpdateRequest, +} from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { queryClient } from "@/lib/query-client/client"; + +// Cache invalidation strategy: +// - Automation writes invalidate the search-space list + the touched detail. +// - Trigger writes only invalidate the parent automation detail (triggers +// come back inline in AutomationDetail). +// We deliberately invalidate the whole "automations" prefix on the list side +// because list is keyed by (searchSpaceId, limit, offset) and we don't track +// the active pagination in this layer. + +function invalidateList(searchSpaceId: number) { + queryClient.invalidateQueries({ queryKey: ["automations", "list", searchSpaceId] }); +} + +function invalidateDetail(automationId: number) { + queryClient.invalidateQueries({ + queryKey: cacheKeys.automations.detail(automationId), + }); +} + +export const createAutomationMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (request: AutomationCreateRequest) => { + return automationsApiService.createAutomation(request); + }, + onSuccess: (_, variables) => { + invalidateList(variables.search_space_id); + toast.success("Automation created"); + }, + onError: (error: Error) => { + console.error("Error creating automation:", error); + toast.error("Failed to create automation"); + }, +})); + +export const updateAutomationMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; patch: AutomationUpdateRequest }) => { + return automationsApiService.updateAutomation(vars.automationId, vars.patch); + }, + onSuccess: (automation, vars) => { + invalidateDetail(vars.automationId); + invalidateList(automation.search_space_id); + toast.success("Automation updated"); + }, + onError: (error: Error) => { + console.error("Error updating automation:", error); + toast.error("Failed to update automation"); + }, +})); + +export const deleteAutomationMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; searchSpaceId: number }) => { + await automationsApiService.deleteAutomation(vars.automationId); + return vars; + }, + onSuccess: (vars) => { + invalidateList(vars.searchSpaceId); + invalidateDetail(vars.automationId); + toast.success("Automation deleted"); + }, + onError: (error: Error) => { + console.error("Error deleting automation:", error); + toast.error("Failed to delete automation"); + }, +})); + +export const addTriggerMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; payload: TriggerCreateRequest }) => { + return automationsApiService.addTrigger(vars.automationId, vars.payload); + }, + onSuccess: (_, vars) => { + invalidateDetail(vars.automationId); + toast.success("Trigger added"); + }, + onError: (error: Error) => { + console.error("Error adding trigger:", error); + toast.error("Failed to add trigger"); + }, +})); + +export const updateTriggerMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { + automationId: number; + triggerId: number; + patch: TriggerUpdateRequest; + }) => { + return automationsApiService.updateTrigger(vars.automationId, vars.triggerId, vars.patch); + }, + onSuccess: (_, vars) => { + invalidateDetail(vars.automationId); + toast.success("Trigger updated"); + }, + onError: (error: Error) => { + console.error("Error updating trigger:", error); + toast.error("Failed to update trigger"); + }, +})); + +export const removeTriggerMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; triggerId: number }) => { + await automationsApiService.removeTrigger(vars.automationId, vars.triggerId); + return vars; + }, + onSuccess: (vars) => { + invalidateDetail(vars.automationId); + toast.success("Trigger removed"); + }, + onError: (error: Error) => { + console.error("Error removing trigger:", error); + toast.error("Failed to remove trigger"); + }, +})); diff --git a/surfsense_web/atoms/automations/automations-query.atoms.ts b/surfsense_web/atoms/automations/automations-query.atoms.ts new file mode 100644 index 000000000..4117f9bc8 --- /dev/null +++ b/surfsense_web/atoms/automations/automations-query.atoms.ts @@ -0,0 +1,31 @@ +import { atomWithQuery } from "jotai-tanstack-query"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +// First page of the active search space's automations. +// Detail + paginated/parameterized reads live in hooks (see use-automation.ts, +// use-automation-runs.ts) so atoms stay tied to "current scope" and don't +// proliferate atom families for every (id, limit, offset) tuple. +const DEFAULT_LIMIT = 50; +const DEFAULT_OFFSET = 0; + +export const automationsListAtom = atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + + return { + queryKey: cacheKeys.automations.list(Number(searchSpaceId ?? 0), DEFAULT_LIMIT, DEFAULT_OFFSET), + enabled: !!searchSpaceId, + staleTime: 60 * 1000, + queryFn: async () => { + if (!searchSpaceId) { + return { items: [], total: 0 }; + } + return automationsApiService.listAutomations({ + search_space_id: Number(searchSpaceId), + limit: DEFAULT_LIMIT, + offset: DEFAULT_OFFSET, + }); + }, + }; +}); diff --git a/surfsense_web/contracts/types/automation.types.ts b/surfsense_web/contracts/types/automation.types.ts new file mode 100644 index 000000000..a93249735 --- /dev/null +++ b/surfsense_web/contracts/types/automation.types.ts @@ -0,0 +1,193 @@ +import { z } from "zod"; + +// ============================================================================= +// Enums — mirror app/automations/persistence/enums/* +// ============================================================================= + +export const automationStatus = z.enum(["active", "paused", "archived"]); +export type AutomationStatus = z.infer<typeof automationStatus>; + +export const triggerType = z.enum(["schedule", "manual"]); +export type TriggerType = z.infer<typeof triggerType>; + +export const runStatus = z.enum([ + "pending", + "running", + "succeeded", + "failed", + "cancelled", + "timed_out", +]); +export type RunStatus = z.infer<typeof runStatus>; + +// ============================================================================= +// Definition envelope — mirror app/automations/schemas/definition/* +// ============================================================================= + +export const planStep = z.object({ + step_id: z.string().min(1), + action: z.string().min(1), + when: z.string().nullable().optional(), + params: z.record(z.string(), z.any()).default({}), + output_as: z.string().nullable().optional(), + max_retries: z.number().int().min(0).nullable().optional(), + timeout_seconds: z.number().int().positive().nullable().optional(), +}); +export type PlanStep = z.infer<typeof planStep>; + +export const definitionTriggerSpec = z.object({ + type: z.string().min(1), + params: z.record(z.string(), z.any()).default({}), +}); +export type DefinitionTriggerSpec = z.infer<typeof definitionTriggerSpec>; + +export const execution = z.object({ + timeout_seconds: z.number().int().positive().default(600), + max_retries: z.number().int().min(0).default(2), + retry_backoff: z.enum(["exponential", "linear", "none"]).default("exponential"), + concurrency: z.enum(["drop_if_running", "queue", "always"]).default("drop_if_running"), + on_failure: z.array(planStep).default([]), +}); +export type Execution = z.infer<typeof execution>; + +// Backend ``Metadata`` is ``extra="allow"`` — keep ``tags`` typed, accept arbitrary keys. +export const metadata = z.object({ tags: z.array(z.string()).default([]) }).catchall(z.any()); +export type Metadata = z.infer<typeof metadata>; + +// Backend ``Inputs`` serializes its ``schema_`` field as ``schema`` (alias). +export const inputs = z.object({ + schema: z.record(z.string(), z.any()), +}); +export type Inputs = z.infer<typeof inputs>; + +export const automationDefinition = z.object({ + schema_version: z.string().default("1.0"), + name: z.string().min(1).max(200), + goal: z.string().nullable().optional(), + inputs: inputs.nullable().optional(), + triggers: z.array(definitionTriggerSpec).default([]), + plan: z.array(planStep).min(1), + execution: execution.default(execution.parse({})), + metadata: metadata.default(metadata.parse({})), +}); +export type AutomationDefinition = z.infer<typeof automationDefinition>; + +// ============================================================================= +// Triggers (sub-resource) — mirror app/automations/schemas/api/trigger.py +// ============================================================================= + +export const triggerCreateRequest = z.object({ + type: triggerType, + params: z.record(z.string(), z.any()).default({}), + static_inputs: z.record(z.string(), z.any()).default({}), + enabled: z.boolean().default(true), +}); +export type TriggerCreateRequest = z.infer<typeof triggerCreateRequest>; + +export const triggerUpdateRequest = z.object({ + enabled: z.boolean().nullable().optional(), + params: z.record(z.string(), z.any()).nullable().optional(), + static_inputs: z.record(z.string(), z.any()).nullable().optional(), +}); +export type TriggerUpdateRequest = z.infer<typeof triggerUpdateRequest>; + +export const trigger = z.object({ + id: z.number(), + type: triggerType, + params: z.record(z.string(), z.any()), + static_inputs: z.record(z.string(), z.any()), + enabled: z.boolean(), + last_fired_at: z.string().nullable().optional(), + next_fire_at: z.string().nullable().optional(), + created_at: z.string(), +}); +export type Trigger = z.infer<typeof trigger>; + +// ============================================================================= +// Automations — mirror app/automations/schemas/api/automation.py +// ============================================================================= + +export const automationCreateRequest = z.object({ + search_space_id: z.number(), + name: z.string().min(1).max(200), + description: z.string().nullable().optional(), + definition: automationDefinition, + triggers: z.array(triggerCreateRequest).default([]), +}); +export type AutomationCreateRequest = z.infer<typeof automationCreateRequest>; + +export const automationUpdateRequest = z.object({ + name: z.string().min(1).max(200).nullable().optional(), + description: z.string().nullable().optional(), + status: automationStatus.nullable().optional(), + definition: automationDefinition.nullable().optional(), +}); +export type AutomationUpdateRequest = z.infer<typeof automationUpdateRequest>; + +export const automationSummary = z.object({ + id: z.number(), + search_space_id: z.number(), + name: z.string(), + description: z.string().nullable().optional(), + status: automationStatus, + version: z.number(), + created_at: z.string(), + updated_at: z.string(), +}); +export type AutomationSummary = z.infer<typeof automationSummary>; + +export const automation = automationSummary.extend({ + definition: automationDefinition, + triggers: z.array(trigger).default([]), +}); +export type Automation = z.infer<typeof automation>; + +export const automationListResponse = z.object({ + items: z.array(automationSummary), + total: z.number(), +}); +export type AutomationListResponse = z.infer<typeof automationListResponse>; + +export const automationListParams = z.object({ + search_space_id: z.number(), + limit: z.number().int().min(1).max(200).default(50), + offset: z.number().int().min(0).default(0), +}); +export type AutomationListParams = z.infer<typeof automationListParams>; + +// ============================================================================= +// Runs (sub-resource) — mirror app/automations/schemas/api/run.py +// ============================================================================= + +export const runSummary = z.object({ + id: z.number(), + automation_id: z.number(), + trigger_id: z.number().nullable().optional(), + status: runStatus, + started_at: z.string().nullable().optional(), + finished_at: z.string().nullable().optional(), + created_at: z.string(), +}); +export type RunSummary = z.infer<typeof runSummary>; + +export const run = runSummary.extend({ + definition_snapshot: z.record(z.string(), z.any()), + inputs: z.record(z.string(), z.any()), + step_results: z.array(z.record(z.string(), z.any())), + output: z.record(z.string(), z.any()).nullable().optional(), + artifacts: z.array(z.record(z.string(), z.any())), + error: z.record(z.string(), z.any()).nullable().optional(), +}); +export type Run = z.infer<typeof run>; + +export const runListResponse = z.object({ + items: z.array(runSummary), + total: z.number(), +}); +export type RunListResponse = z.infer<typeof runListResponse>; + +export const runListParams = z.object({ + limit: z.number().int().min(1).max(200).default(50), + offset: z.number().int().min(0).default(0), +}); +export type RunListParams = z.infer<typeof runListParams>; diff --git a/surfsense_web/hooks/use-automation-runs.ts b/surfsense_web/hooks/use-automation-runs.ts new file mode 100644 index 000000000..c91c7bd6e --- /dev/null +++ b/surfsense_web/hooks/use-automation-runs.ts @@ -0,0 +1,42 @@ +"use client"; +import { useQuery } from "@tanstack/react-query"; +import type { Run, RunListResponse } from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +const DEFAULT_LIMIT = 50; +const DEFAULT_OFFSET = 0; + +export interface UseAutomationRunsOptions { + limit?: number; + offset?: number; + enabled?: boolean; +} + +/** Paginated run history for one automation. Newest-first per backend. */ +export function useAutomationRuns( + automationId: number | undefined, + { limit = DEFAULT_LIMIT, offset = DEFAULT_OFFSET, enabled = true }: UseAutomationRunsOptions = {} +) { + return useQuery<RunListResponse, Error>({ + queryKey: cacheKeys.automations.runs(automationId ?? 0, limit, offset), + queryFn: () => automationsApiService.listRuns(automationId as number, { limit, offset }), + enabled: enabled && !!automationId, + staleTime: 30_000, + }); +} + +/** Single run with the full snapshot, step results, output and artifacts. */ +export function useAutomationRun( + automationId: number | undefined, + runId: number | undefined, + options: { enabled?: boolean } = {} +) { + const { enabled = true } = options; + return useQuery<Run, Error>({ + queryKey: cacheKeys.automations.run(automationId ?? 0, runId ?? 0), + queryFn: () => automationsApiService.getRun(automationId as number, runId as number), + enabled: enabled && !!automationId && !!runId, + staleTime: 30_000, + }); +} diff --git a/surfsense_web/hooks/use-automation.ts b/surfsense_web/hooks/use-automation.ts new file mode 100644 index 000000000..d49ec03a1 --- /dev/null +++ b/surfsense_web/hooks/use-automation.ts @@ -0,0 +1,19 @@ +"use client"; +import { useQuery } from "@tanstack/react-query"; +import type { Automation } from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +/** + * Fetch a single automation with its definition and triggers. + * Lives outside the jotai atom layer because it's keyed by id, not by the + * "current scope" the atom layer assumes. + */ +export function useAutomation(automationId: number | undefined) { + return useQuery<Automation, Error>({ + queryKey: cacheKeys.automations.detail(automationId ?? 0), + queryFn: () => automationsApiService.getAutomation(automationId as number), + enabled: !!automationId, + staleTime: 60_000, + }); +} diff --git a/surfsense_web/hooks/use-automations.ts b/surfsense_web/hooks/use-automations.ts new file mode 100644 index 000000000..945e91866 --- /dev/null +++ b/surfsense_web/hooks/use-automations.ts @@ -0,0 +1,24 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { automationsListAtom } from "@/atoms/automations/automations-query.atoms"; + +/** + * List automations in the active search space (first page). + * Pagination knobs live in detail/list hooks below; v1 surfaces only the + * first page since automation counts are expected to be small. + */ +export function useAutomations() { + const { data, isLoading, error, refetch } = useAutomationsRaw(); + return { + automations: data?.items ?? [], + total: data?.total ?? 0, + loading: isLoading, + error, + refresh: refetch, + }; +} + +// Exposed for callers that prefer the raw react-query result shape. +export function useAutomationsRaw() { + return useAtomValue(automationsListAtom); +} diff --git a/surfsense_web/lib/apis/automations-api.service.ts b/surfsense_web/lib/apis/automations-api.service.ts new file mode 100644 index 000000000..ebe72bea5 --- /dev/null +++ b/surfsense_web/lib/apis/automations-api.service.ts @@ -0,0 +1,102 @@ +import { + type AutomationCreateRequest, + type AutomationListParams, + type AutomationUpdateRequest, + automation, + automationCreateRequest, + automationListResponse, + automationUpdateRequest, + type RunListParams, + run, + runListResponse, + type TriggerCreateRequest, + type TriggerUpdateRequest, + trigger, + triggerCreateRequest, + triggerUpdateRequest, +} from "@/contracts/types/automation.types"; +import { ValidationError } from "../error"; +import { baseApiService } from "./base-api.service"; + +const BASE = "/api/v1/automations"; + +function rejectIfInvalid<T>( + parsed: { success: true; data: T } | { success: false; error: { issues: { message: string }[] } } +): T { + if (!parsed.success) { + throw new ValidationError( + `Invalid request: ${parsed.error.issues.map((i) => i.message).join(", ")}` + ); + } + return parsed.data; +} + +class AutomationsApiService { + // ---- Automations --------------------------------------------------------- + + listAutomations = async (params: AutomationListParams) => { + const qs = new URLSearchParams({ + search_space_id: String(params.search_space_id), + limit: String(params.limit), + offset: String(params.offset), + }); + return baseApiService.get(`${BASE}?${qs.toString()}`, automationListResponse); + }; + + getAutomation = async (automationId: number) => { + return baseApiService.get(`${BASE}/${automationId}`, automation); + }; + + createAutomation = async (request: AutomationCreateRequest) => { + const data = rejectIfInvalid(automationCreateRequest.safeParse(request)); + return baseApiService.post(BASE, automation, { body: data }); + }; + + updateAutomation = async (automationId: number, request: AutomationUpdateRequest) => { + const data = rejectIfInvalid(automationUpdateRequest.safeParse(request)); + return baseApiService.patch(`${BASE}/${automationId}`, automation, { body: data }); + }; + + // Server returns 204; baseApiService now resolves to null and skips schema validation. + deleteAutomation = async (automationId: number) => { + return baseApiService.delete(`${BASE}/${automationId}`); + }; + + // ---- Triggers (sub-resource) -------------------------------------------- + + addTrigger = async (automationId: number, request: TriggerCreateRequest) => { + const data = rejectIfInvalid(triggerCreateRequest.safeParse(request)); + return baseApiService.post(`${BASE}/${automationId}/triggers`, trigger, { body: data }); + }; + + updateTrigger = async ( + automationId: number, + triggerId: number, + request: TriggerUpdateRequest + ) => { + const data = rejectIfInvalid(triggerUpdateRequest.safeParse(request)); + return baseApiService.patch(`${BASE}/${automationId}/triggers/${triggerId}`, trigger, { + body: data, + }); + }; + + removeTrigger = async (automationId: number, triggerId: number) => { + return baseApiService.delete(`${BASE}/${automationId}/triggers/${triggerId}`); + }; + + // ---- Runs (sub-resource, read-only) ------------------------------------- + + listRuns = async (automationId: number, params: RunListParams) => { + const qs = new URLSearchParams({ + limit: String(params.limit), + offset: String(params.offset), + }); + return baseApiService.get(`${BASE}/${automationId}/runs?${qs.toString()}`, runListResponse); + }; + + getRun = async (automationId: number, runId: number) => { + return baseApiService.get(`${BASE}/${automationId}/runs/${runId}`, run); + }; +} + +export const automationsApiService = new AutomationsApiService(); diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index ce45ee143..8943d6842 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -126,4 +126,14 @@ export const cacheKeys = { batchUnreadCounts: (searchSpaceId: number | null) => ["notifications", "unread-counts-batch", searchSpaceId] as const, }, + automations: { + // list endpoint is keyed by pagination too so distinct pages don't collide + list: (searchSpaceId: number, limit: number, offset: number) => + ["automations", "list", searchSpaceId, limit, offset] as const, + detail: (automationId: number) => ["automations", "detail", automationId] as const, + runs: (automationId: number, limit: number, offset: number) => + ["automations", "runs", automationId, limit, offset] as const, + run: (automationId: number, runId: number) => + ["automations", "runs", automationId, runId] as const, + }, }; From fe28833ad47bcff944a44ecdafde394d3247de96 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:02:48 +0200 Subject: [PATCH 069/133] feat(web): automations list page with status, pause/resume and delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vertical slice at /dashboard/[id]/automations. The page is read-only by default; every action gates on backend automations:* permissions via a co-located permissions hook so adding/removing surfaces stays a one-file change. Route: - page.tsx — server boundary; extracts search_space_id. - automations-content.tsx — client orchestrator (loading / no-access / error / empty / table branches). Components (one concern per file): - automations-header.tsx — title + count + "Create via chat" CTA. - automations-table.tsx + automation-row.tsx — name/status/updated columns; row name links to detail (PR4). - automation-status-badge.tsx — active / paused / archived pill. - automation-row-actions.tsx — ⋯ menu with pause/resume + delete, gated on canUpdate / canDelete. Archived rows hide the toggle. - delete-automation-dialog.tsx — destructive confirm; mentions FK cascade explicitly so users know triggers/runs go too. - automations-empty-state.tsx — zero-state pointing to chat (creation is intent-driven via the create_automation HITL tool, not a form). - automations-loading.tsx — skeleton rows in the same shell so the layout doesn't shift on data arrival. - automation-triggers-summary.tsx — small cron-describer (daily, weekdays, weekly, monthly, hourly) + timezone for the detail page. Kept inline since v1 only registers schedule. Hooks: - use-automation-permissions.ts — single source of truth for the slice's canCreate/canRead/canUpdate/canDelete/canExecute gates, backed by myAccessAtom. Pause/resume and delete reuse the PR2 mutation atoms, so list + detail caches stay coherent without bespoke invalidation. Out of scope (later PRs): - detail route (definition viewer + triggers manager) — PR4 - raw JSON editor — PR5 - nav entry / sidebar wiring — small follow-up PR --- .../automations/automations-content.tsx | 101 +++++++++++++++ .../components/automation-row-actions.tsx | 98 ++++++++++++++ .../automations/components/automation-row.tsx | 61 +++++++++ .../components/automation-status-badge.tsx | 49 +++++++ .../automation-triggers-summary.tsx | 120 ++++++++++++++++++ .../components/automations-empty-state.tsx | 42 ++++++ .../components/automations-header.tsx | 44 +++++++ .../components/automations-loading.tsx | 36 ++++++ .../components/automations-table.tsx | 73 +++++++++++ .../components/delete-automation-dialog.tsx | 80 ++++++++++++ .../hooks/use-automation-permissions.ts | 37 ++++++ .../[search_space_id]/automations/page.tsx | 15 +++ 12 files changed, 756 insertions(+) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx new file mode 100644 index 000000000..fa1caff96 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx @@ -0,0 +1,101 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomations } from "@/hooks/use-automations"; +import { AutomationsEmptyState } from "./components/automations-empty-state"; +import { AutomationsHeader } from "./components/automations-header"; +import { AutomationsTable } from "./components/automations-table"; +import { useAutomationPermissions } from "./hooks/use-automation-permissions"; + +interface AutomationsContentProps { + searchSpaceId: number; +} + +/** + * Client orchestrator for the automations list page. Pulls the active + * search space's first page (via ``useAutomations`` → ``automationsListAtom``) + * and the user's permissions, then decides between empty / loading / table. + * + * Read access is mandatory; anything else is hidden behind RBAC. The + * permissions hook is co-located in this slice so adding/removing + * surfaces is a one-file change. + */ +export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { + const { automations, total, loading, error } = useAutomations(); + const perms = useAutomationPermissions(); + + if (perms.loading) { + // Permissions gate the entire page; defer everything until we know. + return ( + <> + <AutomationsHeader searchSpaceId={searchSpaceId} total={0} loading canCreate={false} /> + <AutomationsTable + automations={[]} + searchSpaceId={searchSpaceId} + loading + canUpdate={false} + canDelete={false} + /> + </> + ); + } + + if (!perms.canRead) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to view automations in this search space. + </p> + </div> + ); + } + + if (error) { + return ( + <> + <AutomationsHeader + searchSpaceId={searchSpaceId} + total={0} + loading={false} + canCreate={perms.canCreate} + /> + <div className="rounded-lg border border-destructive/40 bg-destructive/5 px-6 py-8 text-center"> + <p className="text-sm text-destructive">Couldn't load automations. {error.message}</p> + </div> + </> + ); + } + + if (!loading && automations.length === 0) { + return ( + <> + <AutomationsHeader + searchSpaceId={searchSpaceId} + total={0} + loading={false} + canCreate={perms.canCreate} + /> + <AutomationsEmptyState searchSpaceId={searchSpaceId} canCreate={perms.canCreate} /> + </> + ); + } + + return ( + <> + <AutomationsHeader + searchSpaceId={searchSpaceId} + total={total} + loading={loading} + canCreate={perms.canCreate} + /> + <AutomationsTable + automations={automations} + searchSpaceId={searchSpaceId} + loading={loading} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + /> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx new file mode 100644 index 000000000..229a417dc --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx @@ -0,0 +1,98 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { MoreHorizontal, Pause, Play, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import type { AutomationSummary } from "@/contracts/types/automation.types"; +import { DeleteAutomationDialog } from "./delete-automation-dialog"; + +interface AutomationRowActionsProps { + automation: AutomationSummary; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * Three-dot menu on each row: pause/resume (if updatable) and delete + * (if deletable). The menu itself is hidden when the user has neither + * permission so we don't render an empty trigger. + */ +export function AutomationRowActions({ + automation, + searchSpaceId, + canUpdate, + canDelete, +}: AutomationRowActionsProps) { + const { mutateAsync: updateAutomation, isPending: updating } = useAtomValue( + updateAutomationMutationAtom + ); + const [deleteOpen, setDeleteOpen] = useState(false); + + if (!canUpdate && !canDelete) return null; + + const nextStatus = automation.status === "active" ? "paused" : "active"; + const pauseLabel = automation.status === "active" ? "Pause" : "Resume"; + const PauseIcon = automation.status === "active" ? Pause : Play; + const canToggle = canUpdate && automation.status !== "archived"; + + async function handleTogglePause() { + await updateAutomation({ + automationId: automation.id, + patch: { status: nextStatus }, + }); + } + + return ( + <> + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + variant="ghost" + size="icon" + className="h-8 w-8" + aria-label={`Actions for ${automation.name}`} + > + <MoreHorizontal className="h-4 w-4" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="w-40"> + {canToggle && ( + <DropdownMenuItem onSelect={handleTogglePause} disabled={updating}> + <PauseIcon className="mr-2 h-4 w-4" /> + {pauseLabel} + </DropdownMenuItem> + )} + {canToggle && canDelete && <DropdownMenuSeparator />} + {canDelete && ( + <DropdownMenuItem + onSelect={() => setDeleteOpen(true)} + className="text-destructive focus:text-destructive" + > + <Trash2 className="mr-2 h-4 w-4" /> + Delete + </DropdownMenuItem> + )} + </DropdownMenuContent> + </DropdownMenu> + + {canDelete && ( + <DeleteAutomationDialog + open={deleteOpen} + onOpenChange={setDeleteOpen} + automationId={automation.id} + automationName={automation.name} + searchSpaceId={searchSpaceId} + /> + )} + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx new file mode 100644 index 000000000..a59fb4527 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx @@ -0,0 +1,61 @@ +"use client"; +import Link from "next/link"; +import { TableCell, TableRow } from "@/components/ui/table"; +import type { AutomationSummary } from "@/contracts/types/automation.types"; +import { formatRelativeDate } from "@/lib/format-date"; +import { AutomationRowActions } from "./automation-row-actions"; +import { AutomationStatusBadge } from "./automation-status-badge"; + +interface AutomationRowProps { + automation: AutomationSummary; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * One row in the automations table. The name links to the detail page; + * actions are gated by ``canUpdate`` / ``canDelete``. Trigger summary + * is intentionally left to the detail page — list responses don't + * include triggers and we want to avoid N+1 detail fetches. + */ +export function AutomationRow({ + automation, + searchSpaceId, + canUpdate, + canDelete, +}: AutomationRowProps) { + return ( + <TableRow className="border-b border-border/60 hover:bg-muted/40"> + <TableCell className="px-4 md:px-6 py-3 border-r border-border/60"> + <div className="flex flex-col gap-0.5 min-w-0"> + <Link + href={`/dashboard/${searchSpaceId}/automations/${automation.id}`} + className="text-sm font-medium text-foreground hover:underline truncate" + > + {automation.name} + </Link> + {automation.description && ( + <span className="text-xs text-muted-foreground line-clamp-1"> + {automation.description} + </span> + )} + </div> + </TableCell> + <TableCell className="px-4 py-3 border-r border-border/60 w-32"> + <AutomationStatusBadge status={automation.status} /> + </TableCell> + <TableCell className="hidden md:table-cell px-4 py-3 border-r border-border/60 w-40 text-xs text-muted-foreground"> + {formatRelativeDate(automation.updated_at)} + </TableCell> + <TableCell className="px-4 md:px-6 py-3 w-16 text-right"> + <AutomationRowActions + automation={automation} + searchSpaceId={searchSpaceId} + canUpdate={canUpdate} + canDelete={canDelete} + /> + </TableCell> + </TableRow> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx new file mode 100644 index 000000000..ecf171e78 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx @@ -0,0 +1,49 @@ +"use client"; +import { Archive, CircleDot, Pause } from "lucide-react"; +import type { AutomationStatus } from "@/contracts/types/automation.types"; +import { cn } from "@/lib/utils"; + +interface AutomationStatusBadgeProps { + status: AutomationStatus; + className?: string; +} + +// Color + icon per status. Active = green, paused = amber, archived = muted. +const STATUS_STYLES: Record< + AutomationStatus, + { label: string; icon: typeof CircleDot; classes: string } +> = { + active: { + label: "Active", + icon: CircleDot, + classes: + "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-900/50", + }, + paused: { + label: "Paused", + icon: Pause, + classes: + "bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900/50", + }, + archived: { + label: "Archived", + icon: Archive, + classes: "bg-muted text-muted-foreground border border-border/60", + }, +}; + +export function AutomationStatusBadge({ status, className }: AutomationStatusBadgeProps) { + const { label, icon: Icon, classes } = STATUS_STYLES[status]; + return ( + <span + className={cn( + "inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", + classes, + className + )} + > + <Icon className="h-3 w-3" aria-hidden /> + {label} + </span> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx new file mode 100644 index 000000000..ac27b01e2 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx @@ -0,0 +1,120 @@ +"use client"; +import { CalendarClock, Pause } from "lucide-react"; +import type { Trigger } from "@/contracts/types/automation.types"; + +interface AutomationTriggersSummaryProps { + triggers: Trigger[]; +} + +/** + * One-line summary of an automation's triggers for the list view. + * + * v1 only registers ``schedule`` so this stays compact: + * - 0 triggers → "No triggers" + * - 1 schedule trigger → "Mon–Fri at 09:00 · UTC" + disabled badge if off + * - >1 → "N triggers" + * + * The detail page renders the full per-trigger editor. + */ +export function AutomationTriggersSummary({ triggers }: AutomationTriggersSummaryProps) { + if (triggers.length === 0) { + return <span className="text-xs text-muted-foreground">No triggers</span>; + } + + if (triggers.length > 1) { + return <span className="text-xs text-muted-foreground">{triggers.length} triggers</span>; + } + + const [trigger] = triggers; + + if (trigger.type === "schedule") { + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; + const human = cron ? describeCron(cron) : "Schedule"; + + return ( + <span className="inline-flex items-center gap-1.5 text-xs"> + <CalendarClock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden /> + <span className="text-foreground">{human}</span> + <span className="text-muted-foreground">· {tz}</span> + {!trigger.enabled && ( + <span className="inline-flex items-center gap-1 rounded-md border border-border/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"> + <Pause className="h-2.5 w-2.5" aria-hidden /> + Off + </span> + )} + </span> + ); + } + + return <span className="text-xs text-muted-foreground capitalize">{trigger.type}</span>; +} + +// ---------------------------------------------------------------------------- +// Minimal cron describer for the common 5-field patterns SurfSense automations +// surface today. Falls back to the raw expression when unrecognized so the user +// still sees something honest instead of a guess. +// +// Kept inline (not a library) because: +// - v1 only needs to recognize a small set of patterns produced by the +// drafter LLM (hourly/daily/weekdays/weekly/monthly). +// - All current consumers live in this slice. If reuse grows, lift to +// ``lib/cron-describe.ts``. +// ---------------------------------------------------------------------------- + +const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +function describeCron(cron: string): string { + const parts = cron.trim().split(/\s+/); + if (parts.length !== 5) return cron; + + const [minute, hour, dom, month, dow] = parts; + + // Daily at H:MM (matches the very common "0 9 * * *") + if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Daily at ${formatTime(hour, minute)}`; + } + + // Weekdays at H:MM ("0 9 * * 1-5") + if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Mon–Fri at ${formatTime(hour, minute)}`; + } + + // Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5") + if ( + month === "*" && + dom === "*" && + /^\d+$/.test(minute) && + /^\d+$/.test(hour) && + /^[\d,]+$/.test(dow) + ) { + const days = dow + .split(",") + .map((d) => DAY_NAMES[Number(d) % 7]) + .filter(Boolean) + .join(", "); + if (days) return `${days} at ${formatTime(hour, minute)}`; + } + + // Monthly on day N ("0 9 1 * *") + if ( + month === "*" && + dow === "*" && + /^\d+$/.test(dom) && + /^\d+$/.test(hour) && + /^\d+$/.test(minute) + ) { + return `Day ${dom} of each month at ${formatTime(hour, minute)}`; + } + + // Hourly ("0 * * * *") + if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) { + return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`; + } + + return cron; +} + +function formatTime(hour: string, minute: string): string { + return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx new file mode 100644 index 000000000..4004cce9b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -0,0 +1,42 @@ +"use client"; +import { MessageSquarePlus, Workflow } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationsEmptyStateProps { + searchSpaceId: number; + canCreate: boolean; +} + +/** + * Zero-state for the automations list. The primary CTA points to a new + * chat — creation happens via the ``create_automation`` HITL tool, not a + * "new automation" form. We surface the chat path explicitly so users + * don't go hunting for an "add" button that doesn't exist. + */ +export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsEmptyStateProps) { + return ( + <div className="rounded-lg border border-dashed border-border/60 bg-muted/20 px-6 py-12 text-center"> + <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-muted text-muted-foreground"> + <Workflow className="h-6 w-6" aria-hidden /> + </div> + <h3 className="mt-4 text-base font-semibold text-foreground">No automations yet</h3> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + Automations let SurfSense run agent tasks on a schedule. Describe what you want in chat and + SurfSense drafts the automation for your approval. + </p> + {canCreate ? ( + <Button asChild className="mt-6"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + ) : ( + <p className="mt-6 text-xs text-muted-foreground"> + You don't have permission to create automations in this search space. + </p> + )} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx new file mode 100644 index 000000000..b938825a6 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -0,0 +1,44 @@ +"use client"; +import { MessageSquarePlus } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationsHeaderProps { + searchSpaceId: number; + total: number; + loading: boolean; + canCreate: boolean; +} + +/** + * Page header: title + count + "Create via chat" CTA. Creation is intent-driven + * (the create_automation tool runs inside chat with a HITL approval card), so + * the CTA links to a new chat rather than opening a form. + */ +export function AutomationsHeader({ + searchSpaceId, + total, + loading, + canCreate, +}: AutomationsHeaderProps) { + return ( + <div className="flex items-center justify-between gap-4 flex-wrap"> + <div className="flex items-baseline gap-3"> + <h1 className="text-xl md:text-2xl font-semibold text-foreground">Automations</h1> + {!loading && ( + <span className="text-sm text-muted-foreground"> + {total} {total === 1 ? "automation" : "automations"} + </span> + )} + </div> + {canCreate && ( + <Button asChild size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + )} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx new file mode 100644 index 000000000..1156be3f6 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx @@ -0,0 +1,36 @@ +"use client"; +import { Skeleton } from "@/components/ui/skeleton"; +import { TableCell, TableRow } from "@/components/ui/table"; + +const ROW_KEYS = ["sk-1", "sk-2", "sk-3"]; + +/** + * Skeleton rows for the automations table. Number of rows is fixed since + * we don't know the count ahead of time and three placeholders is enough + * to communicate "loading" without flashing too much chrome. + */ +export function AutomationsLoadingRows() { + return ( + <> + {ROW_KEYS.map((key) => ( + <TableRow key={key} className="border-b border-border/60 hover:bg-transparent"> + <TableCell className="px-4 md:px-6 py-3 border-r border-border/60"> + <div className="flex flex-col gap-1.5"> + <Skeleton className="h-4 w-40" /> + <Skeleton className="h-3 w-56" /> + </div> + </TableCell> + <TableCell className="px-4 py-3 border-r border-border/60 w-32"> + <Skeleton className="h-5 w-16 rounded-md" /> + </TableCell> + <TableCell className="hidden md:table-cell px-4 py-3 border-r border-border/60 w-40"> + <Skeleton className="h-3 w-20" /> + </TableCell> + <TableCell className="px-4 md:px-6 py-3 w-16"> + <Skeleton className="h-8 w-8 rounded-md ml-auto" /> + </TableCell> + </TableRow> + ))} + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx new file mode 100644 index 000000000..ec3aeeef5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx @@ -0,0 +1,73 @@ +"use client"; +import { Activity, CalendarDays, Workflow } from "lucide-react"; +import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import type { AutomationSummary } from "@/contracts/types/automation.types"; +import { AutomationRow } from "./automation-row"; +import { AutomationsLoadingRows } from "./automations-loading"; + +interface AutomationsTableProps { + automations: AutomationSummary[]; + searchSpaceId: number; + loading: boolean; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * Table shell + header. Rows render below — loading state renders skeleton + * rows in the same shell so the layout doesn't shift on data arrival. + */ +export function AutomationsTable({ + automations, + searchSpaceId, + loading, + canUpdate, + canDelete, +}: AutomationsTableProps) { + return ( + <div className="rounded-lg border border-border/60 bg-accent overflow-hidden"> + <Table className="table-fixed w-full"> + <TableHeader> + <TableRow className="hover:bg-transparent border-b border-border/60"> + <TableHead className="px-4 md:px-6 border-r border-border/60"> + <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> + <Workflow size={14} className="opacity-60 text-muted-foreground" /> + Name + </span> + </TableHead> + <TableHead className="border-r border-border/60 w-32"> + <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> + <Activity size={14} className="opacity-60 text-muted-foreground" /> + Status + </span> + </TableHead> + <TableHead className="hidden md:table-cell border-r border-border/60 w-40"> + <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> + <CalendarDays size={14} className="opacity-60 text-muted-foreground" /> + Updated + </span> + </TableHead> + <TableHead className="px-4 md:px-6 w-16"> + <span className="sr-only">Actions</span> + </TableHead> + </TableRow> + </TableHeader> + <TableBody> + {loading ? ( + <AutomationsLoadingRows /> + ) : ( + automations.map((automation) => ( + <AutomationRow + key={automation.id} + automation={automation} + searchSpaceId={searchSpaceId} + canUpdate={canUpdate} + canDelete={canDelete} + /> + )) + )} + </TableBody> + </Table> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx new file mode 100644 index 000000000..db73ddad5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx @@ -0,0 +1,80 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { deleteAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Spinner } from "@/components/ui/spinner"; + +interface DeleteAutomationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + automationId: number; + automationName: string; + searchSpaceId: number; +} + +/** + * Confirm + delete one automation. FK cascade on the backend wipes attached + * triggers and runs, so we mention it explicitly. List re-fetch is handled + * by the mutation atom's onSuccess. + */ +export function DeleteAutomationDialog({ + open, + onOpenChange, + automationId, + automationName, + searchSpaceId, +}: DeleteAutomationDialogProps) { + const { mutateAsync: deleteAutomation } = useAtomValue(deleteAutomationMutationAtom); + const [submitting, setSubmitting] = useState(false); + + async function handleConfirm() { + setSubmitting(true); + try { + await deleteAutomation({ automationId, searchSpaceId }); + onOpenChange(false); + } finally { + setSubmitting(false); + } + } + + return ( + <AlertDialog open={open} onOpenChange={onOpenChange}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>Delete this automation?</AlertDialogTitle> + <AlertDialogDescription> + <span className="font-medium text-foreground">{automationName}</span> and all of its + triggers and run history will be removed. This cannot be undone. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel disabled={submitting}>Cancel</AlertDialogCancel> + <AlertDialogAction + onClick={handleConfirm} + disabled={submitting} + className="bg-destructive text-white hover:bg-destructive/90" + > + {submitting ? ( + <span className="inline-flex items-center gap-2"> + <Spinner size="xs" /> + Deleting… + </span> + ) : ( + "Delete" + )} + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts b/surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts new file mode 100644 index 000000000..293688710 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts @@ -0,0 +1,37 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { canPerform, myAccessAtom } from "@/atoms/members/members-query.atoms"; + +/** + * Centralized RBAC gates for the automations slice. Co-located with the + * route so adding/removing surfaces stays a one-file change. Backed by + * the same ``myAccessAtom`` the rest of the app uses; owners short-circuit + * to ``true`` for every action. + * + * Mirrors backend permissions in ``app.db.permissions`` (automations:*). + */ +export interface AutomationPermissions { + loading: boolean; + canCreate: boolean; + canRead: boolean; + canUpdate: boolean; + canDelete: boolean; + canExecute: boolean; +} + +export function useAutomationPermissions(): AutomationPermissions { + const { data: access, isLoading } = useAtomValue(myAccessAtom); + + return useMemo( + () => ({ + loading: isLoading, + canCreate: canPerform(access, "automations:create"), + canRead: canPerform(access, "automations:read"), + canUpdate: canPerform(access, "automations:update"), + canDelete: canPerform(access, "automations:delete"), + canExecute: canPerform(access, "automations:execute"), + }), + [access, isLoading] + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx new file mode 100644 index 000000000..b77cb20f4 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx @@ -0,0 +1,15 @@ +import { AutomationsContent } from "./automations-content"; + +export default async function AutomationsPage({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationsContent searchSpaceId={Number(search_space_id)} /> + </div> + ); +} From 7bc52dcdc04124dfff9b598eee5c33dfed488044 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:11:20 +0200 Subject: [PATCH 070/133] feat(web): surface Automations in the sidebar under Inbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an "Automations" nav entry rendered explicitly between Inbox and (on mobile) Documents, mirroring how those two are pulled out of the nav list and rendered above the chat sections. The icon is Workflow to match settings/RBAC labelling. LayoutDataProvider: - Adds the entry to navItems pointing at /dashboard/[id]/automations. - Marks isActive via pathname so the row highlights on the route. - Tags /automations as a workspace-panel page so it renders in the centered settings-style viewport (same chrome as Team / settings). Sidebar: - Pulls out automationsItem alongside inboxItem and documentsItem. - Renders it between them. - Excludes its URL from footerNavItems so it doesn't double-render. Page-level RBAC still gates the actual view; the sidebar entry is always visible (consistent with Inbox/Documents which are also not gated at the nav layer). Anonymous (FreeLayoutDataProvider) intentionally not touched — automations is an authenticated feature. --- .../layout/providers/LayoutDataProvider.tsx | 34 ++++++++++++++----- .../components/layout/ui/sidebar/Sidebar.tsx | 28 ++++++++++++--- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 917d1c6e1..67971e435 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -2,7 +2,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { AlertTriangle, Inbox, LibraryBig } from "lucide-react"; +import { AlertTriangle, Inbox, LibraryBig, Workflow } from "lucide-react"; import { useParams, usePathname, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useTheme } from "next-themes"; @@ -335,9 +335,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid }, [threadsData, searchSpaceId]); // Navigation items - // Inbox is rendered explicitly below "New chat" in the sidebar (it is also - // surfaced in the icon rail's collapsed mode via this list). Announcements - // has been moved to the avatar dropdown and is no longer a nav item. + // Inbox, Automations, and Documents are rendered explicitly below "New chat" + // in the sidebar (also surfaced in the icon rail's collapsed mode via this + // list). Announcements has been moved to the avatar dropdown. + const isAutomationsActive = pathname?.includes("/automations") === true; const navItems: NavItem[] = useMemo( () => ( @@ -349,6 +350,12 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid isActive: isInboxSidebarOpen, badge: totalUnreadCount > 0 ? formatInboxCount(totalUnreadCount) : undefined, }, + { + title: "Automations", + url: `/dashboard/${searchSpaceId}/automations`, + icon: Workflow, + isActive: isAutomationsActive, + }, isMobile ? { title: "Documents", @@ -359,7 +366,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid : null, ] as (NavItem | null)[] ).filter((item): item is NavItem => item !== null), - [isMobile, isInboxSidebarOpen, isDocumentsSidebarOpen, totalUnreadCount] + [ + isMobile, + isInboxSidebarOpen, + isDocumentsSidebarOpen, + totalUnreadCount, + searchSpaceId, + isAutomationsActive, + ] ); // Handlers @@ -660,12 +674,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid const isUserSettingsPage = pathname?.includes("/user-settings") === true; const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true; const isTeamPage = pathname?.endsWith("/team") === true; + const isAutomationsPage = pathname?.includes("/automations") === true; const useWorkspacePanel = pathname?.endsWith("/buy-more") === true || pathname?.endsWith("/more-pages") === true || isUserSettingsPage || isSearchSpaceSettingsPage || - isTeamPage; + isTeamPage || + isAutomationsPage; return ( <> @@ -705,12 +721,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid isChatPage={isChatPage} useWorkspacePanel={useWorkspacePanel} workspacePanelViewportClassName={ - isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage + isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage ? "items-start justify-center px-6 py-8 md:px-10 md:py-10" : undefined } workspacePanelContentClassName={ - isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage ? "max-w-5xl" : undefined + isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage + ? "max-w-5xl" + : undefined } isLoadingChats={isLoadingThreads} activeSlideoutPanel={activeSlideoutPanel} diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index e0cb3072a..805f8bfd3 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -140,16 +140,26 @@ export function Sidebar({ const t = useTranslations("sidebar"); const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null); - // Inbox and Documents are rendered explicitly right below New Chat. Pull - // them out of the nav items list so they don't also appear in the bottom - // NavSection. Documents is only present in navItems on mobile. + // Inbox, Automations, and Documents are rendered explicitly right below + // New Chat. Pull them out of the nav items list so they don't also appear + // in the bottom NavSection. Documents is only present in navItems on + // mobile; Automations is identified by URL suffix so the same code path + // works across search spaces. const inboxItem = useMemo(() => navItems.find((item) => item.url === "#inbox"), [navItems]); + const automationsItem = useMemo( + () => navItems.find((item) => item.url.endsWith("/automations")), + [navItems] + ); const documentsItem = useMemo( () => navItems.find((item) => item.url === "#documents"), [navItems] ); const footerNavItems = useMemo( - () => navItems.filter((item) => item.url !== "#inbox" && item.url !== "#documents"), + () => + navItems.filter( + (item) => + item.url !== "#inbox" && item.url !== "#documents" && !item.url.endsWith("/automations") + ), [navItems] ); @@ -227,6 +237,16 @@ export function Sidebar({ } /> )} + {automationsItem && ( + <SidebarButton + icon={automationsItem.icon} + label={automationsItem.title} + onClick={() => onNavItemClick?.(automationsItem)} + isCollapsed={isCollapsed} + isActive={automationsItem.isActive} + tooltipContent={isCollapsed ? automationsItem.title : undefined} + /> + )} {documentsItem && ( <SidebarButton icon={documentsItem.icon} From bc3c2fd5152628c0e12a36fccac56a43adc131ed Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:14:10 +0200 Subject: [PATCH 071/133] fix(web): hide header Create CTA on the automations empty state The empty-state card already hosts the primary "Create via chat" CTA; keeping the header button on the same screen showed two identical buttons. Adds an optional ``showCreateCta`` prop to AutomationsHeader (default true) and turns it off only in the empty branch so the card stays the focal point. --- .../automations/automations-content.tsx | 1 + .../automations/components/automations-header.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx index fa1caff96..756221d38 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx @@ -75,6 +75,7 @@ export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { total={0} loading={false} canCreate={perms.canCreate} + showCreateCta={false} /> <AutomationsEmptyState searchSpaceId={searchSpaceId} canCreate={perms.canCreate} /> </> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index b938825a6..22ea60664 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -8,6 +8,12 @@ interface AutomationsHeaderProps { total: number; loading: boolean; canCreate: boolean; + /** + * Render the header's Create CTA. Defaults to true; the empty state owns + * the primary CTA on its own card, so the orchestrator turns this off + * there to avoid a duplicate button. + */ + showCreateCta?: boolean; } /** @@ -20,6 +26,7 @@ export function AutomationsHeader({ total, loading, canCreate, + showCreateCta = true, }: AutomationsHeaderProps) { return ( <div className="flex items-center justify-between gap-4 flex-wrap"> @@ -31,7 +38,7 @@ export function AutomationsHeader({ </span> )} </div> - {canCreate && ( + {canCreate && showCreateCta && ( <Button asChild size="sm"> <Link href={`/dashboard/${searchSpaceId}/new-chat`}> <MessageSquarePlus className="mr-2 h-4 w-4" /> From c0a9ea368f43af57e12342a49d17d102c2642b85 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:21:54 +0200 Subject: [PATCH 072/133] feat(web): automations detail page (definition viewer + trigger manager) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vertical slice at /dashboard/[id]/automations/[automation_id]. Branches in the orchestrator are: perms loading → skeleton, no-access → access denied panel, bad id → not-found, fetch loading → skeleton, fetch error → not-found, loaded → header + definition + triggers. Route: - page.tsx — server boundary; extracts both ids. - automation-detail-content.tsx — client orchestrator. Header: - automation-detail-header.tsx — back link, name, status badge, description, pause/resume + delete actions. Delete navigates back to the list via a new onDeleted hook on DeleteAutomationDialog so the list page (where the row just vanishes) stays unaffected. - automation-not-found.tsx — 404/403/NaN-id panel. We don't distinguish missing vs. forbidden in the UI. Definition (read-only in v1): - automation-definition-section.tsx — wrapper Card; renders goal + tags + execution defaults + inputs schema (if present) + plan. - plan-step-card.tsx — one step (when, output_as, retries, timeout, params JSON). - execution-summary.tsx — timeout / max_retries / backoff / concurrency + on_failure step count. - inputs-schema-preview.tsx — formatted JSON of inputs.schema; only rendered when the definition declares inputs. Triggers: - automation-triggers-section.tsx — wrapper Card, "Add via chat" CTA (creation is intent-driven, same philosophy as automations). - trigger-card.tsx — schedule + timezone + cron, last/next fire hints, static_inputs JSON, enable Switch and remove button. - delete-trigger-dialog.tsx — confirm + mutation atom. Shared: - lib/describe-cron.ts — moved out of automation-triggers-summary.tsx so both list and detail can describe schedules consistently (daily/weekdays/weekly/monthly/hourly, raw cron fallback). Loading: - automation-detail-loading.tsx — same shell as the loaded view so the layout doesn't jump on data arrival. RBAC: each interactive surface is independently gated (canUpdate/canDelete/canCreate) so the orchestrator stays thin and the component tree is self-documenting about what each action requires. Out of scope (later PRs): - Editing definition / trigger params (raw-JSON path) — PR5 - Run history — PR6 --- .../automation-detail-content.tsx | 86 ++++++++++ .../automation-definition-section.tsx | 99 ++++++++++++ .../components/automation-detail-header.tsx | 129 +++++++++++++++ .../components/automation-detail-loading.tsx | 42 +++++ .../components/automation-not-found.tsx | 34 ++++ .../automation-triggers-section.tsx | 75 +++++++++ .../components/delete-trigger-dialog.tsx | 80 +++++++++ .../components/execution-summary.tsx | 37 +++++ .../components/inputs-schema-preview.tsx | 20 +++ .../components/plan-step-card.tsx | 73 +++++++++ .../components/trigger-card.tsx | 152 ++++++++++++++++++ .../automations/[automation_id]/page.tsx | 18 +++ .../automation-triggers-summary.tsx | 70 +------- .../components/delete-automation-dialog.tsx | 8 + .../automations/lib/describe-cron.ts | 66 ++++++++ 15 files changed, 920 insertions(+), 69 deletions(-) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx new file mode 100644 index 000000000..a82887721 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -0,0 +1,86 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomation } from "@/hooks/use-automation"; +import { useAutomationPermissions } from "../hooks/use-automation-permissions"; +import { AutomationDefinitionSection } from "./components/automation-definition-section"; +import { AutomationDetailHeader } from "./components/automation-detail-header"; +import { AutomationDetailLoading } from "./components/automation-detail-loading"; +import { AutomationNotFound } from "./components/automation-not-found"; +import { AutomationTriggersSection } from "./components/automation-triggers-section"; + +interface AutomationDetailContentProps { + searchSpaceId: number; + automationId: number; +} + +/** + * Client orchestrator for one automation's detail view. Branches: + * - permissions loading → skeleton + * - no read permission → access denied panel + * - bad id (NaN) → not-found panel + * - detail fetching → skeleton + * - detail error / null → not-found panel (we don't distinguish 404 + * from 403 in the UI) + * - detail loaded → header + definition + triggers + * + * Each child component is gated independently on the relevant permission + * so the orchestrator stays thin. + */ +export function AutomationDetailContent({ + searchSpaceId, + automationId, +}: AutomationDetailContentProps) { + const perms = useAutomationPermissions(); + const validId = Number.isInteger(automationId) && automationId > 0; + const { data: automation, isLoading, error } = useAutomation(validId ? automationId : undefined); + + if (perms.loading) { + return <AutomationDetailLoading />; + } + + if (!perms.canRead) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to view automations in this search space. + </p> + </div> + ); + } + + if (!validId) { + return <AutomationNotFound searchSpaceId={searchSpaceId} />; + } + + if (isLoading) { + return <AutomationDetailLoading />; + } + + if (error || !automation) { + return <AutomationNotFound searchSpaceId={searchSpaceId} error={error} />; + } + + return ( + <> + <AutomationDetailHeader + automation={automation} + searchSpaceId={searchSpaceId} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + /> + + <AutomationDefinitionSection definition={automation.definition} /> + + <AutomationTriggersSection + triggers={automation.triggers} + automationId={automation.id} + searchSpaceId={searchSpaceId} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + canCreate={perms.canCreate} + /> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx new file mode 100644 index 000000000..9545f363b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx @@ -0,0 +1,99 @@ +"use client"; +import { ListOrdered, Settings2, Tag, Target } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { AutomationDefinition } from "@/contracts/types/automation.types"; +import { ExecutionSummary } from "./execution-summary"; +import { InputsSchemaPreview } from "./inputs-schema-preview"; +import { PlanStepCard } from "./plan-step-card"; + +interface AutomationDefinitionSectionProps { + definition: AutomationDefinition; +} + +/** + * The Definition card. Read-only in v1 — editing definitions happens via + * chat (re-run create_automation with a refined intent) or, later, via + * the raw-JSON path. Layout is top-down: + * goal → tags → execution defaults → inputs schema (if any) → plan + * + * The schema_version is rendered as a small badge next to the section + * title so it's discoverable but doesn't fight for attention. + */ +export function AutomationDefinitionSection({ definition }: AutomationDefinitionSectionProps) { + const hasTags = definition.metadata.tags.length > 0; + const hasInputs = !!definition.inputs; + + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <CardTitle className="text-base font-semibold">Definition</CardTitle> + <span className="text-xs font-mono text-muted-foreground border border-border/60 rounded px-1.5 py-0.5"> + v{definition.schema_version} + </span> + </CardHeader> + <CardContent className="space-y-6"> + {definition.goal && ( + <Field icon={Target} label="Goal"> + <p className="text-sm text-foreground">{definition.goal}</p> + </Field> + )} + + {hasTags && ( + <Field icon={Tag} label="Tags"> + <div className="flex flex-wrap gap-1.5"> + {definition.metadata.tags.map((tag) => ( + <span + key={tag} + className="inline-flex items-center rounded-md bg-muted px-2 py-0.5 text-xs text-muted-foreground" + > + {tag} + </span> + ))} + </div> + </Field> + )} + + <Field icon={Settings2} label="Execution defaults"> + <ExecutionSummary execution={definition.execution} /> + </Field> + + {hasInputs && ( + <Field icon={Settings2} label="Inputs schema"> + {definition.inputs && <InputsSchemaPreview inputs={definition.inputs} />} + </Field> + )} + + <Field + icon={ListOrdered} + label={`Plan · ${definition.plan.length} step${definition.plan.length === 1 ? "" : "s"}`} + > + <div className="space-y-2"> + {definition.plan.map((step, idx) => ( + <PlanStepCard key={step.step_id} step={step} index={idx} /> + ))} + </div> + </Field> + </CardContent> + </Card> + ); +} + +function Field({ + icon: Icon, + label, + children, +}: { + icon: typeof Target; + label: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider"> + <Icon className="h-3.5 w-3.5" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx new file mode 100644 index 000000000..4cf3efcc1 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx @@ -0,0 +1,129 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { ArrowLeft, Pause, Play, Trash2 } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useCallback, useState } from "react"; +import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/spinner"; +import type { Automation } from "@/contracts/types/automation.types"; +import { AutomationStatusBadge } from "../../components/automation-status-badge"; +import { DeleteAutomationDialog } from "../../components/delete-automation-dialog"; + +interface AutomationDetailHeaderProps { + automation: Automation; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * Title bar for the detail page: back link, name, status badge, + * description, and the two destructive-ish primary actions (pause / + * resume + delete). Same mutation atoms as the list-row actions to + * keep caches coherent. + * + * Archived automations hide the pause/resume toggle (we don't unarchive + * here — that flow comes later if we need it). + */ +export function AutomationDetailHeader({ + automation, + searchSpaceId, + canUpdate, + canDelete, +}: AutomationDetailHeaderProps) { + const router = useRouter(); + const { mutateAsync: updateAutomation, isPending: updating } = useAtomValue( + updateAutomationMutationAtom + ); + const [deleteOpen, setDeleteOpen] = useState(false); + + const canToggle = canUpdate && automation.status !== "archived"; + const nextStatus = automation.status === "active" ? "paused" : "active"; + const pauseLabel = automation.status === "active" ? "Pause" : "Resume"; + const PauseIcon = automation.status === "active" ? Pause : Play; + + const handleDeleted = useCallback(() => { + router.push(`/dashboard/${searchSpaceId}/automations`); + }, [router, searchSpaceId]); + + async function handleTogglePause() { + await updateAutomation({ + automationId: automation.id, + patch: { status: nextStatus }, + }); + } + + return ( + <> + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link + href={`/dashboard/${searchSpaceId}/automations`} + className="text-xs text-muted-foreground" + > + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automations + </Link> + </Button> + + <div className="flex items-start justify-between gap-4 flex-wrap"> + <div className="space-y-2 min-w-0 flex-1"> + <div className="flex items-center gap-3 flex-wrap"> + <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> + {automation.name} + </h1> + <AutomationStatusBadge status={automation.status} /> + </div> + {automation.description && ( + <p className="text-sm text-muted-foreground max-w-3xl">{automation.description}</p> + )} + </div> + + <div className="flex items-center gap-2 shrink-0"> + {canToggle && ( + <Button + type="button" + variant="outline" + size="sm" + onClick={handleTogglePause} + disabled={updating} + > + {updating ? ( + <Spinner size="xs" className="mr-2" /> + ) : ( + <PauseIcon className="mr-2 h-4 w-4" /> + )} + {pauseLabel} + </Button> + )} + {canDelete && ( + <Button + type="button" + variant="outline" + size="sm" + onClick={() => setDeleteOpen(true)} + className="text-destructive hover:text-destructive hover:bg-destructive/10" + > + <Trash2 className="mr-2 h-4 w-4" /> + Delete + </Button> + )} + </div> + </div> + </div> + + {canDelete && ( + <DeleteAutomationDialog + open={deleteOpen} + onOpenChange={setDeleteOpen} + automationId={automation.id} + automationName={automation.name} + searchSpaceId={searchSpaceId} + onDeleted={handleDeleted} + /> + )} + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx new file mode 100644 index 000000000..1d01305ee --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx @@ -0,0 +1,42 @@ +"use client"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; + +/** + * Skeleton for the detail page. Same shell as the loaded view (header + + * two stacked cards) so the layout doesn't jump on data arrival. + */ +export function AutomationDetailLoading() { + return ( + <div className="space-y-6"> + <div className="space-y-3"> + <Skeleton className="h-4 w-32" /> + <div className="flex items-center gap-3"> + <Skeleton className="h-7 w-64" /> + <Skeleton className="h-5 w-16 rounded-md" /> + </div> + <Skeleton className="h-4 w-96" /> + </div> + + <Card> + <CardHeader> + <Skeleton className="h-5 w-32" /> + </CardHeader> + <CardContent className="space-y-4"> + <Skeleton className="h-4 w-3/4" /> + <Skeleton className="h-4 w-1/2" /> + <Skeleton className="h-24 w-full" /> + </CardContent> + </Card> + + <Card> + <CardHeader> + <Skeleton className="h-5 w-24" /> + </CardHeader> + <CardContent> + <Skeleton className="h-20 w-full" /> + </CardContent> + </Card> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx new file mode 100644 index 000000000..1681caf25 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx @@ -0,0 +1,34 @@ +"use client"; +import { ArrowLeft, FileWarning } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationNotFoundProps { + searchSpaceId: number; + error?: Error | null; +} + +/** + * Rendered when the detail fetch fails (404 / 403 / network) or the id + * is not a number. We don't distinguish "missing" from "forbidden" in the + * UI on purpose — leaking that an id exists you can't read is worse than + * a vague message. + */ +export function AutomationNotFound({ searchSpaceId, error }: AutomationNotFoundProps) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <FileWarning className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Automation not found</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + This automation doesn't exist or you don't have access to it. + {error?.message ? ` (${error.message})` : null} + </p> + <Button asChild variant="outline" size="sm" className="mt-6"> + <Link href={`/dashboard/${searchSpaceId}/automations`}> + <ArrowLeft className="mr-2 h-4 w-4" /> + Back to automations + </Link> + </Button> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx new file mode 100644 index 000000000..8cc62f5c8 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx @@ -0,0 +1,75 @@ +"use client"; +import { CalendarClock, MessageSquarePlus } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Trigger } from "@/contracts/types/automation.types"; +import { TriggerCard } from "./trigger-card"; + +interface AutomationTriggersSectionProps { + triggers: Trigger[]; + automationId: number; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; + canCreate: boolean; +} + +/** + * The Triggers card. Lists each attached trigger with its own enable + * toggle and remove button. Adding a new trigger is intent-driven (via + * chat) for v1 — same philosophy as creating an automation, so the + * empty/add CTA links to a new chat rather than opening a form. + */ +export function AutomationTriggersSection({ + triggers, + automationId, + searchSpaceId, + canUpdate, + canDelete, + canCreate, +}: AutomationTriggersSectionProps) { + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <div className="space-y-1"> + <CardTitle className="text-base font-semibold">Triggers</CardTitle> + <p className="text-xs text-muted-foreground"> + When this automation fires. v1 supports scheduled triggers only. + </p> + </div> + {canCreate && ( + <Button asChild variant="outline" size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Add via chat + </Link> + </Button> + )} + </CardHeader> + <CardContent> + {triggers.length === 0 ? ( + <div className="rounded-md border border-dashed border-border/60 bg-muted/20 px-4 py-8 text-center"> + <CalendarClock className="mx-auto h-8 w-8 text-muted-foreground" aria-hidden /> + <p className="mt-2 text-sm font-medium text-foreground">No triggers attached</p> + <p className="mt-1 text-xs text-muted-foreground"> + This automation can still be invoked, but nothing will fire it on its own. + </p> + </div> + ) : ( + <div className="space-y-3"> + {triggers.map((trigger) => ( + <TriggerCard + key={trigger.id} + trigger={trigger} + automationId={automationId} + canUpdate={canUpdate} + canDelete={canDelete} + /> + ))} + </div> + )} + </CardContent> + </Card> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx new file mode 100644 index 000000000..71e905724 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx @@ -0,0 +1,80 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { removeTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Spinner } from "@/components/ui/spinner"; + +interface DeleteTriggerDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + automationId: number; + triggerId: number; + triggerLabel: string; +} + +/** + * Confirm + detach one trigger from its automation. The automation itself + * is untouched; only this trigger row is removed. The mutation atom + * invalidates the parent automation detail so the page rerenders. + */ +export function DeleteTriggerDialog({ + open, + onOpenChange, + automationId, + triggerId, + triggerLabel, +}: DeleteTriggerDialogProps) { + const { mutateAsync: removeTrigger } = useAtomValue(removeTriggerMutationAtom); + const [submitting, setSubmitting] = useState(false); + + async function handleConfirm() { + setSubmitting(true); + try { + await removeTrigger({ automationId, triggerId }); + onOpenChange(false); + } finally { + setSubmitting(false); + } + } + + return ( + <AlertDialog open={open} onOpenChange={onOpenChange}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>Remove this trigger?</AlertDialogTitle> + <AlertDialogDescription> + <span className="font-medium text-foreground">{triggerLabel}</span> will be detached. + The automation itself stays, but it won't fire on this trigger anymore. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel disabled={submitting}>Cancel</AlertDialogCancel> + <AlertDialogAction + onClick={handleConfirm} + disabled={submitting} + className="bg-destructive text-white hover:bg-destructive/90" + > + {submitting ? ( + <span className="inline-flex items-center gap-2"> + <Spinner size="xs" /> + Removing… + </span> + ) : ( + "Remove" + )} + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx new file mode 100644 index 000000000..5c4dc381c --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx @@ -0,0 +1,37 @@ +"use client"; +import type { Execution } from "@/contracts/types/automation.types"; + +interface ExecutionSummaryProps { + execution: Execution; +} + +/** + * Compact view of an automation's execution defaults (wall-clock cap, + * retries, backoff, concurrency, on_failure presence). Per-step overrides + * are shown inside each PlanStepCard, not here. + */ +export function ExecutionSummary({ execution }: ExecutionSummaryProps) { + return ( + <dl className="grid grid-cols-2 md:grid-cols-4 gap-x-6 gap-y-2 text-xs"> + <Item label="Timeout" value={`${execution.timeout_seconds}s`} /> + <Item label="Max retries" value={String(execution.max_retries)} /> + <Item label="Retry backoff" value={execution.retry_backoff} /> + <Item label="Concurrency" value={execution.concurrency} /> + {execution.on_failure.length > 0 && ( + <Item + label="On failure" + value={`${execution.on_failure.length} step${execution.on_failure.length === 1 ? "" : "s"}`} + /> + )} + </dl> + ); +} + +function Item({ label, value }: { label: string; value: string }) { + return ( + <div className="flex flex-col gap-0.5 min-w-0"> + <dt className="text-muted-foreground">{label}</dt> + <dd className="text-foreground font-medium truncate">{value}</dd> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx new file mode 100644 index 000000000..bf2db8986 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx @@ -0,0 +1,20 @@ +"use client"; +import type { Inputs } from "@/contracts/types/automation.types"; + +interface InputsSchemaPreviewProps { + inputs: Inputs; +} + +/** + * Read-only JSON preview of an automation's accepted-inputs schema. + * Most automations don't define inputs (defaults are baked into the + * trigger's static_inputs), so the parent skips rendering this card + * when ``inputs`` is null. + */ +export function InputsSchemaPreview({ inputs }: InputsSchemaPreviewProps) { + return ( + <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-72"> + {JSON.stringify(inputs.schema, null, 2)} + </pre> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx new file mode 100644 index 000000000..3feb77712 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx @@ -0,0 +1,73 @@ +"use client"; +import { ArrowRightCircle, GitCommitHorizontal } from "lucide-react"; +import type { PlanStep } from "@/contracts/types/automation.types"; + +interface PlanStepCardProps { + step: PlanStep; + index: number; +} + +/** + * Read-only view of one plan step. Renders the step_id + action prominently, + * then a definition list of the per-step knobs, and finally the params as + * formatted JSON. Editable mode is out of scope here — definition edits live + * on the (future) raw-JSON path. + */ +export function PlanStepCard({ step, index }: PlanStepCardProps) { + return ( + <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="flex items-center gap-2 px-4 py-2 border-b border-border/60 bg-muted/30"> + <span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"> + {index + 1} + </span> + <span className="text-sm font-medium text-foreground">{step.step_id}</span> + <ArrowRightCircle className="h-3.5 w-3.5 text-muted-foreground" aria-hidden /> + <span className="text-xs font-mono text-muted-foreground">{step.action}</span> + </div> + + <div className="px-4 py-3 space-y-3"> + {(step.when || + step.output_as || + step.max_retries != null || + step.timeout_seconds != null) && ( + <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5 text-xs"> + {step.when && ( + <DefRow label="When" value={<code className="font-mono">{step.when}</code>} /> + )} + {step.output_as && ( + <DefRow + label="Output as" + value={<code className="font-mono">{step.output_as}</code>} + /> + )} + {step.max_retries != null && ( + <DefRow label="Max retries" value={String(step.max_retries)} /> + )} + {step.timeout_seconds != null && ( + <DefRow label="Timeout" value={`${step.timeout_seconds}s`} /> + )} + </dl> + )} + + <div> + <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1.5"> + <GitCommitHorizontal className="h-3.5 w-3.5" aria-hidden /> + Params + </div> + <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> + {JSON.stringify(step.params, null, 2)} + </pre> + </div> + </div> + </div> + ); +} + +function DefRow({ label, value }: { label: string; value: React.ReactNode }) { + return ( + <div className="flex items-baseline gap-2 min-w-0"> + <dt className="text-muted-foreground shrink-0">{label}:</dt> + <dd className="text-foreground min-w-0 truncate">{value}</dd> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx new file mode 100644 index 000000000..0caaf968f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -0,0 +1,152 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { CalendarClock, Clock, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import type { Trigger } from "@/contracts/types/automation.types"; +import { formatRelativeDate } from "@/lib/format-date"; +import { describeCron } from "../../lib/describe-cron"; +import { DeleteTriggerDialog } from "./delete-trigger-dialog"; + +interface TriggerCardProps { + trigger: Trigger; + automationId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * One trigger row in the Triggers section of the detail page. Renders: + * - type icon + human-readable schedule + timezone + * - last_fired_at / next_fire_at hints + * - static_inputs as formatted JSON (when present) + * - enable toggle + remove button (each gated independently) + * + * Editing params (cron, timezone, static_inputs) lives behind the future + * raw-JSON path; this card stays read-only-except-for-toggle for v1. + */ +export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: TriggerCardProps) { + const { mutateAsync: updateTrigger, isPending: updating } = + useAtomValue(updateTriggerMutationAtom); + const [deleteOpen, setDeleteOpen] = useState(false); + + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; + const human = cron ? describeCron(cron) : trigger.type; + const triggerLabel = cron ? `${human} · ${tz}` : trigger.type; + const hasStaticInputs = Object.keys(trigger.static_inputs ?? {}).length > 0; + + async function handleToggle(checked: boolean) { + await updateTrigger({ + automationId, + triggerId: trigger.id, + patch: { enabled: checked }, + }); + } + + return ( + <> + <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="flex items-center justify-between gap-4 px-4 py-3 border-b border-border/60"> + <div className="flex items-center gap-3 min-w-0"> + <CalendarClock className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> + <div className="min-w-0"> + <div className="flex items-center gap-2 text-sm"> + <span className="font-medium text-foreground">{human}</span> + <span className="text-muted-foreground">· {tz}</span> + </div> + {cron && <code className="text-xs font-mono text-muted-foreground">{cron}</code>} + </div> + </div> + + <div className="flex items-center gap-2 shrink-0"> + {canUpdate && ( + <div className="flex items-center gap-2"> + <span className="text-xs text-muted-foreground"> + {trigger.enabled ? "Enabled" : "Off"} + </span> + <Switch + checked={trigger.enabled} + onCheckedChange={handleToggle} + disabled={updating} + aria-label={trigger.enabled ? "Disable trigger" : "Enable trigger"} + /> + </div> + )} + {canDelete && ( + <Button + variant="ghost" + size="icon" + className="h-8 w-8 text-muted-foreground hover:text-destructive" + onClick={() => setDeleteOpen(true)} + aria-label="Remove trigger" + > + <Trash2 className="h-4 w-4" /> + </Button> + )} + </div> + </div> + + <div className="px-4 py-3 space-y-3 text-xs"> + {(trigger.last_fired_at || trigger.next_fire_at) && ( + <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5"> + {trigger.next_fire_at && ( + <TimeRow label="Next fire" iso={trigger.next_fire_at} highlight={trigger.enabled} /> + )} + {trigger.last_fired_at && <TimeRow label="Last fired" iso={trigger.last_fired_at} />} + </dl> + )} + + {hasStaticInputs && ( + <div> + <div className="text-muted-foreground mb-1">Static inputs</div> + <pre className="rounded-md bg-muted/40 px-3 py-2 font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> + {JSON.stringify(trigger.static_inputs, null, 2)} + </pre> + </div> + )} + </div> + </div> + + {canDelete && ( + <DeleteTriggerDialog + open={deleteOpen} + onOpenChange={setDeleteOpen} + automationId={automationId} + triggerId={trigger.id} + triggerLabel={triggerLabel} + /> + )} + </> + ); +} + +function TimeRow({ + label, + iso, + highlight = false, +}: { + label: string; + iso: string; + highlight?: boolean; +}) { + return ( + <div className="flex items-baseline gap-2 min-w-0"> + <dt className="text-muted-foreground shrink-0 inline-flex items-center gap-1"> + <Clock className="h-3 w-3" aria-hidden /> + {label}: + </dt> + <dd + className={ + highlight + ? "text-foreground font-medium min-w-0 truncate" + : "text-muted-foreground min-w-0 truncate" + } + > + {formatRelativeDate(iso)} + </dd> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx new file mode 100644 index 000000000..dbaceecdd --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx @@ -0,0 +1,18 @@ +import { AutomationDetailContent } from "./automation-detail-content"; + +export default async function AutomationDetailPage({ + params, +}: { + params: Promise<{ search_space_id: string; automation_id: string }>; +}) { + const { search_space_id, automation_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationDetailContent + searchSpaceId={Number(search_space_id)} + automationId={Number(automation_id)} + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx index ac27b01e2..8b61a1e02 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx @@ -1,6 +1,7 @@ "use client"; import { CalendarClock, Pause } from "lucide-react"; import type { Trigger } from "@/contracts/types/automation.types"; +import { describeCron } from "../lib/describe-cron"; interface AutomationTriggersSummaryProps { triggers: Trigger[]; @@ -49,72 +50,3 @@ export function AutomationTriggersSummary({ triggers }: AutomationTriggersSummar return <span className="text-xs text-muted-foreground capitalize">{trigger.type}</span>; } - -// ---------------------------------------------------------------------------- -// Minimal cron describer for the common 5-field patterns SurfSense automations -// surface today. Falls back to the raw expression when unrecognized so the user -// still sees something honest instead of a guess. -// -// Kept inline (not a library) because: -// - v1 only needs to recognize a small set of patterns produced by the -// drafter LLM (hourly/daily/weekdays/weekly/monthly). -// - All current consumers live in this slice. If reuse grows, lift to -// ``lib/cron-describe.ts``. -// ---------------------------------------------------------------------------- - -const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - -function describeCron(cron: string): string { - const parts = cron.trim().split(/\s+/); - if (parts.length !== 5) return cron; - - const [minute, hour, dom, month, dow] = parts; - - // Daily at H:MM (matches the very common "0 9 * * *") - if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { - return `Daily at ${formatTime(hour, minute)}`; - } - - // Weekdays at H:MM ("0 9 * * 1-5") - if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { - return `Mon–Fri at ${formatTime(hour, minute)}`; - } - - // Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5") - if ( - month === "*" && - dom === "*" && - /^\d+$/.test(minute) && - /^\d+$/.test(hour) && - /^[\d,]+$/.test(dow) - ) { - const days = dow - .split(",") - .map((d) => DAY_NAMES[Number(d) % 7]) - .filter(Boolean) - .join(", "); - if (days) return `${days} at ${formatTime(hour, minute)}`; - } - - // Monthly on day N ("0 9 1 * *") - if ( - month === "*" && - dow === "*" && - /^\d+$/.test(dom) && - /^\d+$/.test(hour) && - /^\d+$/.test(minute) - ) { - return `Day ${dom} of each month at ${formatTime(hour, minute)}`; - } - - // Hourly ("0 * * * *") - if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) { - return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`; - } - - return cron; -} - -function formatTime(hour: string, minute: string): string { - return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`; -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx index db73ddad5..23fc522ca 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx @@ -20,6 +20,12 @@ interface DeleteAutomationDialogProps { automationId: number; automationName: string; searchSpaceId: number; + /** + * Fired after a successful delete, before the dialog closes. The detail + * page uses this to navigate back to the list (the row simply vanishes + * on the list page so no callback is needed there). + */ + onDeleted?: () => void; } /** @@ -33,6 +39,7 @@ export function DeleteAutomationDialog({ automationId, automationName, searchSpaceId, + onDeleted, }: DeleteAutomationDialogProps) { const { mutateAsync: deleteAutomation } = useAtomValue(deleteAutomationMutationAtom); const [submitting, setSubmitting] = useState(false); @@ -41,6 +48,7 @@ export function DeleteAutomationDialog({ setSubmitting(true); try { await deleteAutomation({ automationId, searchSpaceId }); + onDeleted?.(); onOpenChange(false); } finally { setSubmitting(false); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts b/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts new file mode 100644 index 000000000..e10a99a44 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts @@ -0,0 +1,66 @@ +/** + * Minimal cron describer for the 5-field patterns the SurfSense drafter LLM + * actually produces (daily, weekdays, weekly, monthly, hourly). Falls back + * to the raw expression when unrecognized so the user still sees something + * honest instead of a guess. + * + * Lives in the automations slice because it's a UI display concern with no + * consumers outside it. If reuse grows, lift to ``lib/cron-describe.ts``. + */ + +const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +export function describeCron(cron: string): string { + const parts = cron.trim().split(/\s+/); + if (parts.length !== 5) return cron; + + const [minute, hour, dom, month, dow] = parts; + + // Daily at H:MM ("0 9 * * *") + if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Daily at ${formatTime(hour, minute)}`; + } + + // Weekdays at H:MM ("0 9 * * 1-5") + if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Mon–Fri at ${formatTime(hour, minute)}`; + } + + // Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5") + if ( + month === "*" && + dom === "*" && + /^\d+$/.test(minute) && + /^\d+$/.test(hour) && + /^[\d,]+$/.test(dow) + ) { + const days = dow + .split(",") + .map((d) => DAY_NAMES[Number(d) % 7]) + .filter(Boolean) + .join(", "); + if (days) return `${days} at ${formatTime(hour, minute)}`; + } + + // Monthly on day N ("0 9 1 * *") + if ( + month === "*" && + dow === "*" && + /^\d+$/.test(dom) && + /^\d+$/.test(hour) && + /^\d+$/.test(minute) + ) { + return `Day ${dom} of each month at ${formatTime(hour, minute)}`; + } + + // Hourly ("0 * * * *") + if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) { + return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`; + } + + return cron; +} + +function formatTime(hour: string, minute: string): string { + return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`; +} From 2e572d781855292aa58981417ade345bb4f6fe21 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:32:04 +0200 Subject: [PATCH 073/133] feat(web): create_automation HITL approval card in chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the create loop in chat: the agent describes user intent → the drafter sub-LLM produces an AutomationCreate JSON → this card surfaces a structured preview → approve persists; reject cancels. Edits flow through chat refinement (re-call with a refined intent), not in-card, so the card stays simple and the multi-turn checkpointer carries the context. Tool UI (components/tool-ui/automation/): - create-automation.tsx — entry dispatcher + ApprovalCard chrome (pending/processing/complete/rejected via useHitlPhase) + SavedCard (links to the detail page) + InvalidCard (lists drafter validation issues) + ErrorCard (verbatim message). Rejection result is hidden because the approval card itself shows the rejected phase inline. - automation-draft-preview.tsx — structured preview body: name + description + goal, triggers (humanised cron + tz + static-input keys), plan steps (step_id → action), and a collapsible raw JSON for power users. Wiring: - components/tool-ui/index.ts — re-export. - features/chat-messages/timeline/tool-registry/registry.ts — register create_automation → CreateAutomationToolUI (dynamic import, same pattern as other connector tools). - contracts/enums/toolIcons.tsx — Workflow icon + "Create automation" display name so fallback chrome (and timeline headers) are honest. Shared util: - lib/automations/describe-cron.ts — lifted from the route slice's lib/ folder since both the dashboard slice and the new approval card now render schedule descriptions. Slice imports updated; the now- empty slice lib/ folder is gone. Backend prompt fragments: - main_agent/system_prompt/.../create_automation/description.md and the tool's docstring no longer promise in-card edits. They make the refinement path explicit: if the user wants changes after seeing the draft, they reply in chat and the agent calls the tool again with a refined intent. v1 deliberately excludes: - In-card edit form / right-side edit panel — defer until we see real demand. The chat refinement loop covers the common case. - approve_always / persistent allow rules — automations are a single artifact, not a repeated mutation, so the "trust this kind of call" affordance doesn't apply. --- .../tools/create_automation/description.md | 11 +- .../main_agent/tools/automation/create.py | 8 +- .../components/trigger-card.tsx | 2 +- .../automation-triggers-summary.tsx | 2 +- .../automation/automation-draft-preview.tsx | 183 ++++++++++ .../tool-ui/automation/create-automation.tsx | 328 ++++++++++++++++++ .../components/tool-ui/automation/index.ts | 1 + surfsense_web/components/tool-ui/index.ts | 1 + surfsense_web/contracts/enums/toolIcons.tsx | 5 + .../timeline/tool-registry/registry.ts | 6 + .../lib => lib/automations}/describe-cron.ts | 5 +- 11 files changed, 541 insertions(+), 11 deletions(-) create mode 100644 surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx create mode 100644 surfsense_web/components/tool-ui/automation/create-automation.tsx create mode 100644 surfsense_web/components/tool-ui/automation/index.ts rename surfsense_web/{app/dashboard/[search_space_id]/automations/lib => lib/automations}/describe-cron.ts (88%) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md index 25b4eec47..ce6562c97 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md @@ -1,7 +1,7 @@ - `create_automation` — Draft and author a new automation. You describe the user's intent; a focused drafter inside the tool turns it into the full - automation JSON; the user reviews and edits it on an approval card; on - approval it's saved. All three phases happen in a single tool call. + automation JSON; the user sees a preview on an approval card and chooses + approve or reject. All three phases happen in a single tool call. - Call when the user wants SurfSense to do something on its own: anything recurring or scheduled ("every morning…", "each Monday…", "weekly recap…"). @@ -17,13 +17,16 @@ explicitly ("the Notion parent page id was not specified") so the drafter leaves a placeholder. - Do NOT prompt the user to confirm before calling — the approval card - IS the confirmation. The user can edit any field on the card. + IS the confirmation. The card shows a structured preview plus the raw + JSON; it offers approve/reject only. If the user wants changes after + seeing the draft, they reply in chat and you call this tool again with + a refined `intent` — that's the edit path. - Returns: - `{status: "saved", automation_id, name}` — confirm briefly to the user ("Saved as automation #N — runs <when>."). Don't dump JSON back. - `{status: "rejected", message}` — the user declined on the card. Acknowledge once ("Understood, I didn't create it.") and stop. Do - NOT retry or pitch variants. + NOT retry or pitch variants without a fresh user request. - `{status: "invalid", issues, raw?}` — drafting/validation failed before the card was shown. Read the issues, refine your `intent` with the missing details, call again. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py index 78fedde22..07b579f3b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -66,9 +66,11 @@ def create_create_automation_tool( names, …) it needs. The tool drafts the full automation JSON internally, shows the user - an approval card for review, and persists on approval. Do NOT - prompt the user to confirm before calling — the card IS the - confirmation. The user can edit any field there. + a structured preview on an approval card, and persists on approval. + The card supports approve/reject only — if the user wants edits + after seeing the draft, they say so in chat and you call this tool + again with a refined intent. Do NOT prompt the user to confirm + before calling — the card IS the confirmation. Args: intent: Concrete restatement of the user's request. Include diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index 0caaf968f..afadf589a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -6,8 +6,8 @@ import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutat import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import type { Trigger } from "@/contracts/types/automation.types"; +import { describeCron } from "@/lib/automations/describe-cron"; import { formatRelativeDate } from "@/lib/format-date"; -import { describeCron } from "../../lib/describe-cron"; import { DeleteTriggerDialog } from "./delete-trigger-dialog"; interface TriggerCardProps { diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx index 8b61a1e02..270a1f844 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx @@ -1,7 +1,7 @@ "use client"; import { CalendarClock, Pause } from "lucide-react"; import type { Trigger } from "@/contracts/types/automation.types"; -import { describeCron } from "../lib/describe-cron"; +import { describeCron } from "@/lib/automations/describe-cron"; interface AutomationTriggersSummaryProps { triggers: Trigger[]; diff --git a/surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx b/surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx new file mode 100644 index 000000000..b0b5c8f78 --- /dev/null +++ b/surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx @@ -0,0 +1,183 @@ +"use client"; +import { CalendarClock, ChevronDown, ChevronRight, ListOrdered, Target } from "lucide-react"; +import { useState } from "react"; +import { describeCron } from "@/lib/automations/describe-cron"; + +interface DraftTrigger { + type: string; + params: Record<string, unknown>; + static_inputs: Record<string, unknown>; + enabled: boolean; +} + +interface DraftPlanStep { + step_id: string; + action: string; + when?: string | null; +} + +interface AutomationDraft { + name: string; + description?: string | null; + definition: { + goal?: string | null; + plan: DraftPlanStep[]; + }; + triggers: DraftTrigger[]; +} + +interface AutomationDraftPreviewProps { + draft: AutomationDraft; + /** Full unmodified args dict — surfaced as the "raw JSON" escape hatch. */ + raw: Record<string, unknown>; +} + +/** + * Structured preview of a drafted automation rendered inside the chat + * approval card. + * + * Three layers, top to bottom: + * 1. Name + description (and goal when present). + * 2. Triggers — humanised cron string + timezone + static_inputs hint. + * 3. Plan steps — ordered list of ``step_id → action``. + * + * A "View raw JSON" toggle reveals the full payload for power users who + * want to inspect every field; it's collapsed by default so the card + * stays scannable for the common case. + */ +export function AutomationDraftPreview({ draft, raw }: AutomationDraftPreviewProps) { + const [showRaw, setShowRaw] = useState(false); + + return ( + <div className="space-y-4 text-sm"> + <div className="space-y-1"> + <p className="font-medium text-foreground">{draft.name}</p> + {draft.description && <p className="text-xs text-muted-foreground">{draft.description}</p>} + </div> + + {draft.definition.goal && ( + <Section icon={Target} label="Goal"> + <p className="text-xs text-foreground">{draft.definition.goal}</p> + </Section> + )} + + <Section icon={CalendarClock} label={`Triggers · ${draft.triggers.length}`}> + {draft.triggers.length === 0 ? ( + <p className="text-xs text-muted-foreground"> + No triggers — automation will need one before it can run. + </p> + ) : ( + <ul className="space-y-1.5"> + {draft.triggers.map((trigger) => ( + <li + key={triggerKey(trigger)} + className="rounded-md border border-border/60 bg-background/50 px-3 py-2 text-xs" + > + <TriggerLine trigger={trigger} /> + </li> + ))} + </ul> + )} + </Section> + + <Section + icon={ListOrdered} + label={`Plan · ${draft.definition.plan.length} step${draft.definition.plan.length === 1 ? "" : "s"}`} + > + <ol className="space-y-1 text-xs"> + {draft.definition.plan.map((step, idx) => ( + <li key={step.step_id} className="flex items-start gap-2"> + <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground shrink-0 mt-0.5"> + {idx + 1} + </span> + <div className="min-w-0"> + <span className="font-medium text-foreground">{step.step_id}</span> + <span className="text-muted-foreground"> → </span> + <code className="font-mono text-muted-foreground">{step.action}</code> + {step.when && <span className="ml-2 text-muted-foreground">when {step.when}</span>} + </div> + </li> + ))} + </ol> + </Section> + + <button + type="button" + onClick={() => setShowRaw((value) => !value)} + className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" + > + {showRaw ? ( + <ChevronDown className="h-3 w-3" aria-hidden /> + ) : ( + <ChevronRight className="h-3 w-3" aria-hidden /> + )} + {showRaw ? "Hide raw JSON" : "View raw JSON"} + </button> + {showRaw && ( + <pre className="rounded-md bg-muted/40 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-72"> + {JSON.stringify(raw, null, 2)} + </pre> + )} + </div> + ); +} + +/** + * Stable key derived from the trigger's identifying fields. Drafts are + * static snapshots so collisions only happen if the LLM emits two literally + * identical triggers — harmless in practice. + */ +function triggerKey(trigger: DraftTrigger): string { + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : ""; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : ""; + return `${trigger.type}|${cron}|${tz}`; +} + +function TriggerLine({ trigger }: { trigger: DraftTrigger }) { + if (trigger.type === "schedule") { + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; + const human = cron ? describeCron(cron) : "Schedule"; + const staticKeys = Object.keys(trigger.static_inputs ?? {}); + return ( + <div className="space-y-1"> + <div className="flex items-center gap-2 flex-wrap"> + <span className="font-medium text-foreground">{human}</span> + <span className="text-muted-foreground">· {tz}</span> + {!trigger.enabled && ( + <span className="rounded-md border border-border/60 px-1.5 py-0.5 text-[10px] text-muted-foreground"> + Disabled + </span> + )} + </div> + {cron && <code className="font-mono text-muted-foreground">{cron}</code>} + {staticKeys.length > 0 && ( + <p className="text-muted-foreground"> + Static inputs: <span className="text-foreground">{staticKeys.join(", ")}</span> + </p> + )} + </div> + ); + } + return <span className="capitalize text-foreground">{trigger.type}</span>; +} + +function Section({ + icon: Icon, + label, + children, +}: { + icon: typeof Target; + label: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-1.5"> + <div className="flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground uppercase tracking-wider"> + <Icon className="h-3 w-3" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx new file mode 100644 index 000000000..713c5fd46 --- /dev/null +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -0,0 +1,328 @@ +"use client"; + +import type { ToolCallMessagePartProps } from "@assistant-ui/react"; +import { useAtomValue } from "jotai"; +import { CornerDownLeftIcon, ExternalLink, Workflow } from "lucide-react"; +import Link from "next/link"; +import { useCallback, useEffect, useMemo } from "react"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { TextShimmerLoader } from "@/components/prompt-kit/loader"; +import { Button } from "@/components/ui/button"; +import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; +import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; +import { AutomationDraftPreview } from "./automation-draft-preview"; + +// ---------------------------------------------------------------------------- +// Result discrimination — mirrors the backend return shapes in +// app/agents/multi_agent_chat/main_agent/tools/automation/create.py. +// ---------------------------------------------------------------------------- + +type AutomationCreateContext = { + search_space_id?: number; +}; + +interface SavedResult { + status: "saved"; + automation_id: number; + name: string; +} + +interface RejectedResult { + status: "rejected"; + message?: string; +} + +interface InvalidResult { + status: "invalid"; + issues: string[]; + raw?: unknown; +} + +interface ErrorResult { + status: "error"; + message: string; +} + +type CreateAutomationResult = + | InterruptResult<AutomationCreateContext> + | SavedResult + | RejectedResult + | InvalidResult + | ErrorResult; + +function hasStatus(value: unknown, status: string): boolean { + return ( + typeof value === "object" && + value !== null && + "status" in value && + (value as { status: unknown }).status === status + ); +} + +// ---------------------------------------------------------------------------- +// Approval card — pending → processing → complete / rejected. +// +// v1 deliberately supports only approve/reject. The drafted JSON is complex +// (full plan + triggers) and we already have a multi-turn refinement path via +// chat ("make it run at 10am instead" → the agent re-calls the tool with a +// refined intent). An in-card edit form would duplicate that flow and add UX +// surface area we don't need yet — leave it for the raw-JSON path on the +// detail page. +// ---------------------------------------------------------------------------- + +interface ApprovalCardProps { + args: Record<string, unknown>; + interruptData: InterruptResult<AutomationCreateContext>; + onDecision: (decision: HitlDecision) => void; +} + +function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { + const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); + + const reviewConfig = interruptData.review_configs[0]; + const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; + const canApprove = allowedDecisions.includes("approve"); + const canReject = allowedDecisions.includes("reject"); + + const draft = useMemo(() => extractDraft(args), [args]); + + const handleApprove = useCallback(() => { + if (phase !== "pending" || !canApprove) return; + setProcessing(); + onDecision({ + type: "approve", + edited_action: { + name: interruptData.action_requests[0]?.name ?? "create_automation", + args, + }, + }); + }, [phase, canApprove, setProcessing, onDecision, interruptData, args]); + + const handleReject = useCallback(() => { + if (phase !== "pending" || !canReject) return; + setRejected(); + onDecision({ type: "reject", message: "User rejected the automation draft." }); + }, [phase, canReject, setRejected, onDecision]); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) { + handleApprove(); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [handleApprove]); + + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 transition-[box-shadow] duration-300"> + <div className="flex items-start gap-3 px-5 pt-5 pb-4 select-none"> + <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> + <div className="min-w-0"> + <p className="text-sm font-semibold text-foreground"> + {phase === "rejected" + ? "Automation cancelled" + : phase === "processing" + ? "Saving automation" + : phase === "complete" + ? "Automation saved" + : "Create automation"} + </p> + {phase === "processing" ? ( + <TextShimmerLoader text="Saving automation" size="sm" /> + ) : phase === "complete" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + Automation created from this draft + </p> + ) : phase === "rejected" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + No automation was saved — ask in chat to refine and try again. + </p> + ) : ( + <p className="text-xs text-muted-foreground mt-0.5"> + Review and approve to save. To change anything, reply in chat — I'll redraft. + </p> + )} + </div> + </div> + + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-4"> + <AutomationDraftPreview draft={draft} raw={args} /> + </div> + + {phase === "pending" && ( + <> + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-4 flex items-center gap-2 select-none"> + {canApprove && ( + <Button size="sm" className="rounded-lg gap-1.5" onClick={handleApprove}> + Approve + <CornerDownLeftIcon className="size-3 opacity-60" /> + </Button> + )} + {canReject && ( + <Button + size="sm" + variant="ghost" + className="rounded-lg text-muted-foreground" + onClick={handleReject} + > + Reject + </Button> + )} + </div> + </> + )} + </div> + ); +} + +// ---------------------------------------------------------------------------- +// Terminal result cards. +// ---------------------------------------------------------------------------- + +function SavedCard({ result }: { result: SavedResult }) { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const detailHref = searchSpaceId + ? `/dashboard/${searchSpaceId}/automations/${result.automation_id}` + : null; + + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none"> + <div className="flex items-start gap-3 px-5 pt-5 pb-4"> + <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> + <div className="min-w-0"> + <p className="text-sm font-semibold text-foreground">Automation saved</p> + <p className="text-xs text-muted-foreground mt-0.5">{result.name}</p> + </div> + </div> + {detailHref && ( + <> + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-3"> + <Link + href={detailHref} + className="inline-flex items-center gap-1.5 text-xs text-primary hover:underline" + > + <ExternalLink className="h-3.5 w-3.5" aria-hidden /> + Open automation #{result.automation_id} + </Link> + </div> + </> + )} + </div> + ); +} + +function InvalidCard({ result }: { result: InvalidResult }) { + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none"> + <div className="px-5 pt-5 pb-4"> + <p className="text-sm font-semibold text-destructive">Couldn't draft this automation</p> + <p className="text-xs text-muted-foreground mt-0.5"> + The drafter produced output that didn't validate. I'll refine and retry. + </p> + </div> + {result.issues.length > 0 && ( + <> + <div className="mx-5 h-px bg-border/50" /> + <ul className="px-5 py-3 space-y-1 text-xs text-muted-foreground list-disc list-inside"> + {result.issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </> + )} + </div> + ); +} + +function ErrorCard({ result }: { result: ErrorResult }) { + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none"> + <div className="px-5 pt-5 pb-4"> + <p className="text-sm font-semibold text-destructive">Failed to create automation</p> + </div> + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-4"> + <p className="text-sm text-muted-foreground">{result.message}</p> + </div> + </div> + ); +} + +// ---------------------------------------------------------------------------- +// Entry — dispatches between the approval card and terminal result cards. +// +// Rejection is special: we hide the standalone "rejected" card because the +// approval card itself already transitions to a "rejected" phase inline. A +// second message in the timeline would be noisy. +// ---------------------------------------------------------------------------- + +export const CreateAutomationToolUI = ({ + args, + result, +}: ToolCallMessagePartProps<{ intent: string }, CreateAutomationResult>) => { + const { dispatch } = useHitlDecision(); + + if (!result) return null; + + if (isInterruptResult(result)) { + return ( + <ApprovalCard + args={args as unknown as Record<string, unknown>} + interruptData={result as InterruptResult<AutomationCreateContext>} + onDecision={(decision) => dispatch([decision])} + /> + ); + } + + if (hasStatus(result, "rejected")) return null; + if (hasStatus(result, "saved")) return <SavedCard result={result as SavedResult} />; + if (hasStatus(result, "invalid")) return <InvalidCard result={result as InvalidResult} />; + if (hasStatus(result, "error")) return <ErrorCard result={result as ErrorResult} />; + + return null; +}; + +// ---------------------------------------------------------------------------- +// Helpers. +// ---------------------------------------------------------------------------- + +/** + * Project raw args into the shape ``AutomationDraftPreview`` expects. + * + * The args dict is the full ``AutomationCreate`` payload (minus + * ``search_space_id`` which is injected server-side), so we trust the + * top-level fields but defend against missing nested defaults. + */ +function extractDraft(args: Record<string, unknown>) { + const definition = (args.definition ?? {}) as Record<string, unknown>; + const planSteps = Array.isArray(definition.plan) + ? (definition.plan as Array<Record<string, unknown>>).map((step) => ({ + step_id: String(step.step_id ?? "(unnamed)"), + action: String(step.action ?? ""), + when: typeof step.when === "string" ? step.when : null, + })) + : []; + + const triggers = Array.isArray(args.triggers) + ? (args.triggers as Array<Record<string, unknown>>).map((trigger) => ({ + type: String(trigger.type ?? "schedule"), + params: (trigger.params ?? {}) as Record<string, unknown>, + static_inputs: (trigger.static_inputs ?? {}) as Record<string, unknown>, + enabled: trigger.enabled !== false, + })) + : []; + + return { + name: String(args.name ?? "(unnamed automation)"), + description: typeof args.description === "string" ? args.description : null, + definition: { + goal: typeof definition.goal === "string" ? definition.goal : null, + plan: planSteps, + }, + triggers, + }; +} diff --git a/surfsense_web/components/tool-ui/automation/index.ts b/surfsense_web/components/tool-ui/automation/index.ts new file mode 100644 index 000000000..50cf1a478 --- /dev/null +++ b/surfsense_web/components/tool-ui/automation/index.ts @@ -0,0 +1 @@ +export { CreateAutomationToolUI } from "./create-automation"; diff --git a/surfsense_web/components/tool-ui/index.ts b/surfsense_web/components/tool-ui/index.ts index 4d885a38c..ee5072dad 100644 --- a/surfsense_web/components/tool-ui/index.ts +++ b/surfsense_web/components/tool-ui/index.ts @@ -7,6 +7,7 @@ */ export { Audio } from "./audio"; +export { CreateAutomationToolUI } from "./automation"; export { CreateDropboxFileToolUI, DeleteDropboxFileToolUI } from "./dropbox"; export { type GenerateImageArgs, diff --git a/surfsense_web/contracts/enums/toolIcons.tsx b/surfsense_web/contracts/enums/toolIcons.tsx index bb87be0ba..668cb51cd 100644 --- a/surfsense_web/contracts/enums/toolIcons.tsx +++ b/surfsense_web/contracts/enums/toolIcons.tsx @@ -25,6 +25,7 @@ import { SearchCheck, Send, Trash2, + Workflow, Wrench, } from "lucide-react"; @@ -47,6 +48,8 @@ const TOOL_ICONS: Record<string, LucideIcon> = { scrape_webpage: ScanLine, web_search: Globe, search_surfsense_docs: BookOpen, + // Automations + create_automation: Workflow, // Memory update_memory: Brain, // Filesystem (built-in deepagent + middleware) @@ -150,6 +153,8 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = { scrape_webpage: "Read webpage", web_search: "Search the web", search_surfsense_docs: "Search knowledge base", + // Automations + create_automation: "Create automation", // Memory update_memory: "Update memory", // Calendar diff --git a/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts b/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts index 8acc6b4fa..c4cfe7cd3 100644 --- a/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts +++ b/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts @@ -17,6 +17,11 @@ const UpdateMemoryToolUI = dynamic( () => import("@/components/tool-ui/user-memory").then((m) => ({ default: m.UpdateMemoryToolUI })), { ssr: false } ); +const CreateAutomationToolUI = dynamic( + () => + import("@/components/tool-ui/automation").then((m) => ({ default: m.CreateAutomationToolUI })), + { ssr: false } +); const SandboxExecuteToolUI = dynamic( () => import("@/components/tool-ui/sandbox-execute").then((m) => ({ @@ -184,6 +189,7 @@ const NullTimelineBody: TimelineToolComponent = () => null; */ const TOOLS_BY_NAME = { task: NullTimelineBody, + create_automation: CreateAutomationToolUI, update_memory: UpdateMemoryToolUI, execute: SandboxExecuteToolUI, execute_code: SandboxExecuteToolUI, diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts b/surfsense_web/lib/automations/describe-cron.ts similarity index 88% rename from surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts rename to surfsense_web/lib/automations/describe-cron.ts index e10a99a44..19f7ff991 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts +++ b/surfsense_web/lib/automations/describe-cron.ts @@ -4,8 +4,9 @@ * to the raw expression when unrecognized so the user still sees something * honest instead of a guess. * - * Lives in the automations slice because it's a UI display concern with no - * consumers outside it. If reuse grows, lift to ``lib/cron-describe.ts``. + * Lives under ``lib/automations/`` because both the dashboard slice and the + * chat ``create_automation`` approval card render schedule descriptions — + * keeping the helper outside either feature avoids a layering violation. */ const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; From 4625bd937e02d5f1f6eb7b62fe25fa7a0b657894 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:35:48 +0200 Subject: [PATCH 074/133] feat(web): run history section on automations detail page Recent runs card under triggers. Each row expands lazily to fetch the full run (step results, output, artifacts, error). 20-row cap for now; real pagination lands if usage demands it. --- .../automation-detail-content.tsx | 3 + .../components/automation-runs-section.tsx | 67 ++++++++++ .../components/run-details-panel.tsx | 116 ++++++++++++++++++ .../[automation_id]/components/run-row.tsx | 75 +++++++++++ .../components/run-status-badge.tsx | 57 +++++++++ .../components/runs-loading.tsx | 23 ++++ 6 files changed, 341 insertions(+) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx index a82887721..253d6ae67 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -6,6 +6,7 @@ import { AutomationDefinitionSection } from "./components/automation-definition- import { AutomationDetailHeader } from "./components/automation-detail-header"; import { AutomationDetailLoading } from "./components/automation-detail-loading"; import { AutomationNotFound } from "./components/automation-not-found"; +import { AutomationRunsSection } from "./components/automation-runs-section"; import { AutomationTriggersSection } from "./components/automation-triggers-section"; interface AutomationDetailContentProps { @@ -81,6 +82,8 @@ export function AutomationDetailContent({ canDelete={perms.canDelete} canCreate={perms.canCreate} /> + + <AutomationRunsSection automationId={automation.id} /> </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx new file mode 100644 index 000000000..b6158cab2 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx @@ -0,0 +1,67 @@ +"use client"; +import { History } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useAutomationRuns } from "@/hooks/use-automation-runs"; +import { RunRow } from "./run-row"; +import { RunsLoading } from "./runs-loading"; + +interface AutomationRunsSectionProps { + automationId: number; +} + +const LIMIT = 20; + +/** + * Run history card. Shows the most recent ``LIMIT`` runs; pagination is + * intentionally deferred — for the foreseeable v1 surface (one-trigger + * automations firing daily), 20 covers ~3 weeks of history which is + * enough to tell whether things are working. Real "load more" lands if + * we see usage spike past that. + */ +export function AutomationRunsSection({ automationId }: AutomationRunsSectionProps) { + const { data, isLoading, error } = useAutomationRuns(automationId, { limit: LIMIT }); + const runs = data?.items ?? []; + + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <div className="space-y-1"> + <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> + <History className="h-4 w-4 text-muted-foreground" aria-hidden /> + Recent runs + </CardTitle> + <p className="text-xs text-muted-foreground"> + Most recent first. Click a row to inspect step results, output and artifacts. + </p> + </div> + {!isLoading && !error && data && ( + <span className="text-xs text-muted-foreground">{data.total} total</span> + )} + </CardHeader> + <CardContent> + {isLoading ? ( + <RunsLoading /> + ) : error ? ( + <p className="text-sm text-muted-foreground"> + Couldn't load runs{error.message ? `: ${error.message}` : "."} + </p> + ) : runs.length === 0 ? ( + <div className="rounded-md border border-dashed border-border/60 bg-muted/20 px-4 py-8 text-center"> + <History className="mx-auto h-8 w-8 text-muted-foreground" aria-hidden /> + <p className="mt-2 text-sm font-medium text-foreground">No runs yet</p> + <p className="mt-1 text-xs text-muted-foreground"> + This automation hasn't fired. Once a trigger fires (or you invoke it manually), runs + will appear here. + </p> + </div> + ) : ( + <div className="space-y-2"> + {runs.map((run) => ( + <RunRow key={run.id} run={run} automationId={automationId} /> + ))} + </div> + )} + </CardContent> + </Card> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx new file mode 100644 index 000000000..d1d46900a --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx @@ -0,0 +1,116 @@ +"use client"; +import { AlertCircle, FileOutput, GitCommitHorizontal, Package, Settings2 } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useAutomationRun } from "@/hooks/use-automation-runs"; + +interface RunDetailsPanelProps { + automationId: number; + runId: number; +} + +/** + * Expanded view of a single run. Fetches lazily — the parent only renders + * this once the row is opened, so the list view stays cheap. + * + * We surface the four most actionable sections (error first when present, + * then output, step results, artifacts, inputs). The full + * ``definition_snapshot`` is omitted because it usually mirrors the live + * definition — surfacing it would dominate the panel without informing + * what the user is trying to learn ("did this work? what did it do?"). + */ +export function RunDetailsPanel({ automationId, runId }: RunDetailsPanelProps) { + const { data: run, isLoading, error } = useAutomationRun(automationId, runId); + + if (isLoading) { + return ( + <div className="space-y-3 p-4 bg-muted/20 border-t border-border/60"> + <Skeleton className="h-3 w-32" /> + <Skeleton className="h-24 w-full" /> + </div> + ); + } + + if (error || !run) { + return ( + <div className="p-4 bg-muted/20 border-t border-border/60 text-xs text-muted-foreground"> + Couldn't load run details{error?.message ? `: ${error.message}` : "."} + </div> + ); + } + + const hasError = run.error && Object.keys(run.error).length > 0; + const hasOutput = run.output && Object.keys(run.output).length > 0; + const hasInputs = Object.keys(run.inputs ?? {}).length > 0; + + return ( + <div className="space-y-4 p-4 bg-muted/20 border-t border-border/60"> + {hasError && ( + <Section icon={AlertCircle} label="Error" tone="destructive"> + <JsonBlock value={run.error} /> + </Section> + )} + + {hasOutput && ( + <Section icon={FileOutput} label="Output"> + <JsonBlock value={run.output} /> + </Section> + )} + + <Section icon={GitCommitHorizontal} label={`Step results · ${run.step_results.length}`}> + {run.step_results.length === 0 ? ( + <p className="text-xs text-muted-foreground">No steps recorded.</p> + ) : ( + <JsonBlock value={run.step_results} /> + )} + </Section> + + {run.artifacts.length > 0 && ( + <Section icon={Package} label={`Artifacts · ${run.artifacts.length}`}> + <JsonBlock value={run.artifacts} /> + </Section> + )} + + {hasInputs && ( + <Section icon={Settings2} label="Resolved inputs"> + <JsonBlock value={run.inputs} /> + </Section> + )} + </div> + ); +} + +function Section({ + icon: Icon, + label, + tone = "default", + children, +}: { + icon: typeof AlertCircle; + label: string; + tone?: "default" | "destructive"; + children: React.ReactNode; +}) { + return ( + <div className="space-y-1.5"> + <div + className={ + tone === "destructive" + ? "flex items-center gap-1.5 text-[11px] font-medium text-destructive uppercase tracking-wider" + : "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground uppercase tracking-wider" + } + > + <Icon className="h-3 w-3" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} + +function JsonBlock({ value }: { value: unknown }) { + return ( + <pre className="rounded-md bg-background/60 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> + {JSON.stringify(value, null, 2)} + </pre> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx new file mode 100644 index 000000000..b8d2bcc8b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx @@ -0,0 +1,75 @@ +"use client"; +import { ChevronDown, ChevronRight, Hand } from "lucide-react"; +import { useState } from "react"; +import type { RunSummary } from "@/contracts/types/automation.types"; +import { formatRelativeDate } from "@/lib/format-date"; +import { RunDetailsPanel } from "./run-details-panel"; +import { RunStatusBadge } from "./run-status-badge"; + +interface RunRowProps { + run: RunSummary; + automationId: number; +} + +/** + * One run row. Click to expand → fetches the full run and shows the + * details panel inline. State is local to each row so multiple panels + * can be open at once (or none). + */ +export function RunRow({ run, automationId }: RunRowProps) { + const [open, setOpen] = useState(false); + const duration = computeDuration(run.started_at, run.finished_at); + const startedLabel = run.started_at + ? formatRelativeDate(run.started_at) + : formatRelativeDate(run.created_at); + + return ( + <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <button + type="button" + onClick={() => setOpen((value) => !value)} + className="flex w-full items-center justify-between gap-4 px-4 py-3 text-left hover:bg-muted/30 transition-colors" + aria-expanded={open} + > + <div className="flex items-center gap-3 min-w-0"> + {open ? ( + <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> + ) : ( + <ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> + )} + <RunStatusBadge status={run.status} /> + <span className="text-xs text-muted-foreground truncate">{startedLabel}</span> + </div> + <div className="flex items-center gap-3 shrink-0 text-xs text-muted-foreground"> + {duration && <span className="font-mono">{duration}</span>} + <TriggerSource triggerId={run.trigger_id ?? null} /> + </div> + </button> + + {open && <RunDetailsPanel automationId={automationId} runId={run.id} />} + </div> + ); +} + +function TriggerSource({ triggerId }: { triggerId: number | null }) { + if (triggerId == null) { + return ( + <span className="inline-flex items-center gap-1"> + <Hand className="h-3 w-3" aria-hidden /> + Manual + </span> + ); + } + return <span>via trigger #{triggerId}</span>; +} + +function computeDuration(started: string | null | undefined, finished: string | null | undefined) { + if (!started || !finished) return null; + const ms = new Date(finished).getTime() - new Date(started).getTime(); + if (!Number.isFinite(ms) || ms < 0) return null; + if (ms < 1000) return `${ms}ms`; + if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; + const minutes = Math.floor(ms / 60_000); + const seconds = Math.floor((ms % 60_000) / 1000); + return `${minutes}m ${seconds}s`; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx new file mode 100644 index 000000000..e5532a500 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx @@ -0,0 +1,57 @@ +"use client"; +import { AlertCircle, CheckCircle2, Clock, Loader2, TimerOff, XCircle } from "lucide-react"; +import type { RunStatus } from "@/contracts/types/automation.types"; +import { cn } from "@/lib/utils"; + +const STATUS_STYLES: Record< + RunStatus, + { label: string; icon: typeof CheckCircle2; classes: string; spin?: boolean } +> = { + pending: { + label: "Pending", + icon: Clock, + classes: "bg-muted text-muted-foreground border-border/60", + }, + running: { + label: "Running", + icon: Loader2, + classes: "bg-blue-500/10 text-blue-600 border-blue-500/20", + spin: true, + }, + succeeded: { + label: "Succeeded", + icon: CheckCircle2, + classes: "bg-emerald-500/10 text-emerald-600 border-emerald-500/20", + }, + failed: { + label: "Failed", + icon: XCircle, + classes: "bg-destructive/10 text-destructive border-destructive/20", + }, + cancelled: { + label: "Cancelled", + icon: AlertCircle, + classes: "bg-muted text-muted-foreground border-border/60", + }, + timed_out: { + label: "Timed out", + icon: TimerOff, + classes: "bg-amber-500/10 text-amber-600 border-amber-500/20", + }, +}; + +export function RunStatusBadge({ status, className }: { status: RunStatus; className?: string }) { + const { label, icon: Icon, classes, spin } = STATUS_STYLES[status]; + return ( + <span + className={cn( + "inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium", + classes, + className + )} + > + <Icon className={cn("h-3 w-3", spin && "animate-spin")} aria-hidden /> + {label} + </span> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx new file mode 100644 index 000000000..5cab18f4c --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx @@ -0,0 +1,23 @@ +"use client"; +import { Skeleton } from "@/components/ui/skeleton"; + +const ROW_KEYS = ["a", "b", "c"] as const; + +export function RunsLoading() { + return ( + <div className="space-y-2"> + {ROW_KEYS.map((key) => ( + <div + key={key} + className="flex items-center justify-between gap-4 rounded-md border border-border/60 bg-background/50 px-4 py-3" + > + <div className="flex items-center gap-3"> + <Skeleton className="h-5 w-20 rounded-md" /> + <Skeleton className="h-3 w-32" /> + </div> + <Skeleton className="h-3 w-16" /> + </div> + ))} + </div> + ); +} From ed8d56aa16dc1beba7dfee1e5a93c18a49b29725 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:44:13 +0200 Subject: [PATCH 075/133] feat(web): create automation via raw JSON --- .../components/automations-empty-state.tsx | 22 +++- .../components/automations-header.tsx | 22 +++- .../new/automation-new-content.tsx | 42 ++++++ .../new/components/automation-json-form.tsx | 122 ++++++++++++++++++ .../new/components/automation-new-header.tsx | 42 ++++++ .../automations/new/page.tsx | 15 +++ .../lib/automations/default-template.ts | 44 +++++++ 7 files changed, 295 insertions(+), 14 deletions(-) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx create mode 100644 surfsense_web/lib/automations/default-template.ts diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx index 4004cce9b..83fa52fa8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -1,5 +1,5 @@ "use client"; -import { MessageSquarePlus, Workflow } from "lucide-react"; +import { FileJson, MessageSquarePlus, Workflow } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -26,12 +26,20 @@ export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsE SurfSense drafts the automation for your approval. </p> {canCreate ? ( - <Button asChild className="mt-6"> - <Link href={`/dashboard/${searchSpaceId}/new-chat`}> - <MessageSquarePlus className="mr-2 h-4 w-4" /> - Create via chat - </Link> - </Button> + <div className="mt-6 flex items-center justify-center gap-2 flex-wrap"> + <Button asChild> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + <Button asChild variant="outline"> + <Link href={`/dashboard/${searchSpaceId}/automations/new`}> + <FileJson className="mr-2 h-4 w-4" /> + Create via JSON + </Link> + </Button> + </div> ) : ( <p className="mt-6 text-xs text-muted-foreground"> You don't have permission to create automations in this search space. diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index 22ea60664..544c6b7ac 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -1,5 +1,5 @@ "use client"; -import { MessageSquarePlus } from "lucide-react"; +import { FileJson, MessageSquarePlus } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -39,12 +39,20 @@ export function AutomationsHeader({ )} </div> {canCreate && showCreateCta && ( - <Button asChild size="sm"> - <Link href={`/dashboard/${searchSpaceId}/new-chat`}> - <MessageSquarePlus className="mr-2 h-4 w-4" /> - Create via chat - </Link> - </Button> + <div className="flex items-center gap-2"> + <Button asChild size="sm" variant="outline"> + <Link href={`/dashboard/${searchSpaceId}/automations/new`}> + <FileJson className="mr-2 h-4 w-4" /> + Create via JSON + </Link> + </Button> + <Button asChild size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + </div> )} </div> ); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx new file mode 100644 index 000000000..f03b3f4c8 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -0,0 +1,42 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomationPermissions } from "../hooks/use-automation-permissions"; +import { AutomationJsonForm } from "./components/automation-json-form"; +import { AutomationNewHeader } from "./components/automation-new-header"; + +interface AutomationNewContentProps { + searchSpaceId: number; +} + +/** + * Orchestrator for the raw-JSON create route. Gates on + * ``automations:create`` so users who can't create don't even see the + * form; same panel as the detail page's access-denied state for + * consistency. + */ +export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { + const perms = useAutomationPermissions(); + + if (perms.loading) { + return <div className="h-32 rounded-md border border-border/60 bg-muted/10 animate-pulse" />; + } + + if (!perms.canCreate) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to create automations in this search space. + </p> + </div> + ); + } + + return ( + <> + <AutomationNewHeader searchSpaceId={searchSpaceId} /> + <AutomationJsonForm searchSpaceId={searchSpaceId} /> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx new file mode 100644 index 000000000..845d95166 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx @@ -0,0 +1,122 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { AlertCircle, Code, FileJson, Save } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { createAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { automationCreateRequest } from "@/contracts/types/automation.types"; +import { DEFAULT_AUTOMATION_TEMPLATE } from "@/lib/automations/default-template"; + +interface AutomationJsonFormProps { + searchSpaceId: number; +} + +/** + * Raw-JSON create form. Lets power users skip the chat drafter when they + * already know the shape they want. Flow: + * parse JSON → inject search_space_id → Zod validate → POST → navigate + * + * ``search_space_id`` is injected here rather than required in the pasted + * payload — the user shouldn't have to know their numeric id, and it + * keeps the template copy-paste-friendly across search spaces. + */ +export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { + const router = useRouter(); + const { mutateAsync: createAutomation, isPending } = useAtomValue(createAutomationMutationAtom); + const [text, setText] = useState(() => JSON.stringify(DEFAULT_AUTOMATION_TEMPLATE, null, 2)); + const [issues, setIssues] = useState<string[]>([]); + + function handleFormat() { + try { + const parsed = JSON.parse(text); + setText(JSON.stringify(parsed, null, 2)); + setIssues([]); + } catch (err) { + setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); + } + } + + async function handleSubmit() { + setIssues([]); + + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch (err) { + setIssues([`Invalid JSON: ${(err as Error).message}`]); + return; + } + + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + setIssues(["Root must be a JSON object."]); + return; + } + + const payload = { ...(parsed as Record<string, unknown>), search_space_id: searchSpaceId }; + const result = automationCreateRequest.safeParse(payload); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + + try { + const created = await createAutomation(result.data); + router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); + } catch (err) { + setIssues([(err as Error).message ?? "Submit failed"]); + } + } + + const hasIssues = issues.length > 0; + + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> + <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> + Definition + triggers + </CardTitle> + <Button type="button" variant="outline" size="sm" onClick={handleFormat}> + <Code className="mr-2 h-3.5 w-3.5" /> + Format + </Button> + </CardHeader> + <CardContent className="space-y-4"> + <textarea + value={text} + onChange={(e) => setText(e.target.value)} + spellCheck={false} + rows={24} + className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[16rem]" + aria-label="Automation JSON" + /> + + {hasIssues && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + + <div className="flex items-center justify-end gap-2"> + <Button type="button" onClick={handleSubmit} disabled={isPending} size="sm"> + {isPending ? <Spinner size="xs" className="mr-2" /> : <Save className="mr-2 h-4 w-4" />} + Create automation + </Button> + </div> + </CardContent> + </Card> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx new file mode 100644 index 000000000..aef2744d5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx @@ -0,0 +1,42 @@ +"use client"; +import { ArrowLeft, MessageSquarePlus } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationNewHeaderProps { + searchSpaceId: number; +} + +export function AutomationNewHeader({ searchSpaceId }: AutomationNewHeaderProps) { + return ( + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link + href={`/dashboard/${searchSpaceId}/automations`} + className="text-xs text-muted-foreground" + > + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automations + </Link> + </Button> + + <div className="flex items-start justify-between gap-4 flex-wrap"> + <div className="space-y-1"> + <h1 className="text-xl md:text-2xl font-semibold text-foreground"> + New automation · raw JSON + </h1> + <p className="text-sm text-muted-foreground max-w-2xl"> + Paste an ``AutomationCreate`` payload and submit. Validated against the schema before + save. Prefer natural language? Use chat instead. + </p> + </div> + <Button asChild variant="outline" size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Switch to chat + </Link> + </Button> + </div> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx new file mode 100644 index 000000000..f6e8e0008 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx @@ -0,0 +1,15 @@ +import { AutomationNewContent } from "./automation-new-content"; + +export default async function NewAutomationPage({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationNewContent searchSpaceId={Number(search_space_id)} /> + </div> + ); +} diff --git a/surfsense_web/lib/automations/default-template.ts b/surfsense_web/lib/automations/default-template.ts new file mode 100644 index 000000000..8963992cb --- /dev/null +++ b/surfsense_web/lib/automations/default-template.ts @@ -0,0 +1,44 @@ +/** + * Minimal valid ``AutomationCreate`` skeleton used to seed the raw-JSON + * create form. ``search_space_id`` is omitted on purpose — the form + * injects it from the route so users never have to know their id. + * + * The shape matches the Pydantic ``AutomationCreate`` model less the + * search_space_id field; Zod validates the merged payload before submit. + */ +export const DEFAULT_AUTOMATION_TEMPLATE = { + name: "My automation", + description: null, + definition: { + name: "My automation", + goal: null, + plan: [ + { + step_id: "step_1", + action: "agent_task", + params: { + query: "Summarize new docs added to folder 12 since the last run.", + }, + }, + ], + execution: { + timeout_seconds: 600, + max_retries: 2, + retry_backoff: "exponential", + concurrency: "drop_if_running", + on_failure: [], + }, + metadata: { tags: [] }, + }, + triggers: [ + { + type: "schedule", + params: { + cron: "0 9 * * 1-5", + timezone: "UTC", + }, + static_inputs: {}, + enabled: true, + }, + ], +} as const; From 91962ba879a677e0fcf43bc2e1dc7abe0732b462 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 02:48:47 +0200 Subject: [PATCH 076/133] fix automation run inputs, hitl routing, and detail UI polish --- .../main_agent/tools/automation/create.py | 11 +- .../hitl/approvals/self_gated/request.py | 7 + .../app/automations/dispatch/run.py | 9 +- .../automation-detail-content.tsx | 2 - .../automation-triggers-section.tsx | 33 +-- .../components/trigger-card.tsx | 29 ++- .../tool-ui/automation/create-automation.tsx | 213 ++++++++++++++---- surfsense_web/lib/format-date.ts | 40 +++- 8 files changed, 258 insertions(+), 86 deletions(-) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py index 07b579f3b..173d302e5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -22,11 +22,14 @@ from typing import Any from uuid import UUID from fastapi import HTTPException +from langchain.tools import ToolRuntime from langchain_core.messages import HumanMessage from langchain_core.tools import tool from pydantic import ValidationError -from app.agents.new_chat.tools.hitl import request_approval +from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( + request_approval, +) from app.automations.schemas.api import AutomationCreate from app.automations.services.automation import AutomationService from app.db import User, async_session_maker @@ -56,7 +59,7 @@ def create_create_automation_tool( uid = UUID(user_id) if isinstance(user_id, str) else user_id @tool - async def create_automation(intent: str) -> dict[str, Any]: + async def create_automation(intent: str, runtime: ToolRuntime) -> dict[str, Any]: """Draft + save an automation from a natural-language intent. Use this when the user wants SurfSense to do something on its own @@ -137,6 +140,7 @@ def create_create_automation_tool( tool_name="create_automation", params=card_params, context={"search_space_id": search_space_id}, + tool_call_id=runtime.tool_call_id, ) if result.rejected: @@ -200,6 +204,5 @@ def _extract_json(text: str) -> dict[str, Any] | None: def _format_validation_issues(exc: ValidationError) -> list[str]: return [ - f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}" - for err in exc.errors() + f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}" for err in exc.errors() ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py index 8729ea85b..2f7e3cd35 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py @@ -49,6 +49,7 @@ def request_approval( params: dict[str, Any], context: dict[str, Any] | None = None, trusted_tools: list[str] | None = None, + tool_call_id: str | None = None, ) -> HITLResult: """Pause the graph for user approval and return the user's decision. @@ -64,6 +65,10 @@ def request_approval( forwarded verbatim to the FE for richer card chrome. trusted_tools: Per-session allowlist; when ``tool_name`` is in it the interrupt is skipped and the tool runs immediately. + tool_call_id: Caller's LangChain tool-call id. Required for tools + running directly on the main agent; subagent-mounted tools omit + it (the ``task`` chokepoint stamps it on re-raise — see + :mod:`...checkpointed_subagent_middleware.propagation`). Returns: :class:`HITLResult` with ``rejected=True`` if the user declined or @@ -90,6 +95,8 @@ def request_approval( interrupt_type=action_type, context=context, ) + if tool_call_id: + payload["tool_call_id"] = tool_call_id approval = interrupt(payload) parsed = parse_lc_envelope(approval) diff --git a/surfsense_backend/app/automations/dispatch/run.py b/surfsense_backend/app/automations/dispatch/run.py index e317a13b9..02d0b0356 100644 --- a/surfsense_backend/app/automations/dispatch/run.py +++ b/surfsense_backend/app/automations/dispatch/run.py @@ -67,8 +67,15 @@ async def dispatch_run( def _validate_inputs( definition: AutomationDefinition, inputs: dict[str, Any] ) -> dict[str, Any]: + """Validate merged inputs against the optional declared schema. + + No declared schema → pass through (runtime inputs like ``fired_at`` / + ``last_fired_at`` and trigger ``static_inputs`` must still reach the + template context). Returning ``{}`` here strips them and makes Jinja + blow up on any ``{{ inputs.* }}`` reference. + """ if definition.inputs is None or not definition.inputs.schema_: - return {} + return inputs try: jsonschema.validate(instance=inputs, schema=definition.inputs.schema_) except jsonschema.ValidationError as exc: diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx index 253d6ae67..49df3633e 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -77,10 +77,8 @@ export function AutomationDetailContent({ <AutomationTriggersSection triggers={automation.triggers} automationId={automation.id} - searchSpaceId={searchSpaceId} canUpdate={perms.canUpdate} canDelete={perms.canDelete} - canCreate={perms.canCreate} /> <AutomationRunsSection automationId={automation.id} /> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx index 8cc62f5c8..33c8373a1 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx @@ -1,7 +1,5 @@ "use client"; -import { CalendarClock, MessageSquarePlus } from "lucide-react"; -import Link from "next/link"; -import { Button } from "@/components/ui/button"; +import { CalendarClock } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import type { Trigger } from "@/contracts/types/automation.types"; import { TriggerCard } from "./trigger-card"; @@ -9,43 +7,28 @@ import { TriggerCard } from "./trigger-card"; interface AutomationTriggersSectionProps { triggers: Trigger[]; automationId: number; - searchSpaceId: number; canUpdate: boolean; canDelete: boolean; - canCreate: boolean; } /** * The Triggers card. Lists each attached trigger with its own enable - * toggle and remove button. Adding a new trigger is intent-driven (via - * chat) for v1 — same philosophy as creating an automation, so the - * empty/add CTA links to a new chat rather than opening a form. + * toggle and remove button. v1 attaches triggers at automation-creation + * time only; there is no in-place "add trigger" affordance here. */ export function AutomationTriggersSection({ triggers, automationId, - searchSpaceId, canUpdate, canDelete, - canCreate, }: AutomationTriggersSectionProps) { return ( <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> - <div className="space-y-1"> - <CardTitle className="text-base font-semibold">Triggers</CardTitle> - <p className="text-xs text-muted-foreground"> - When this automation fires. v1 supports scheduled triggers only. - </p> - </div> - {canCreate && ( - <Button asChild variant="outline" size="sm"> - <Link href={`/dashboard/${searchSpaceId}/new-chat`}> - <MessageSquarePlus className="mr-2 h-4 w-4" /> - Add via chat - </Link> - </Button> - )} + <CardHeader className="pb-4"> + <CardTitle className="text-base font-semibold">Triggers</CardTitle> + <p className="text-xs text-muted-foreground"> + When this automation fires. v1 supports scheduled triggers only. + </p> </CardHeader> <CardContent> {triggers.length === 0 ? ( diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index afadf589a..ec0246e49 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import type { Trigger } from "@/contracts/types/automation.types"; import { describeCron } from "@/lib/automations/describe-cron"; -import { formatRelativeDate } from "@/lib/format-date"; +import { formatRelativeDate, formatRelativeFutureDate } from "@/lib/format-date"; import { DeleteTriggerDialog } from "./delete-trigger-dialog"; interface TriggerCardProps { @@ -91,11 +91,18 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri <div className="px-4 py-3 space-y-3 text-xs"> {(trigger.last_fired_at || trigger.next_fire_at) && ( - <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5"> + <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1"> {trigger.next_fire_at && ( - <TimeRow label="Next fire" iso={trigger.next_fire_at} highlight={trigger.enabled} /> + <TimeRow + label="Next fire" + iso={trigger.next_fire_at} + tense="future" + highlight={trigger.enabled} + /> + )} + {trigger.last_fired_at && ( + <TimeRow label="Last fired" iso={trigger.last_fired_at} tense="past" /> )} - {trigger.last_fired_at && <TimeRow label="Last fired" iso={trigger.last_fired_at} />} </dl> )} @@ -126,17 +133,20 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri function TimeRow({ label, iso, + tense, highlight = false, }: { label: string; iso: string; + tense: "past" | "future"; highlight?: boolean; }) { + const formatted = tense === "future" ? formatRelativeFutureDate(iso) : formatRelativeDate(iso); return ( - <div className="flex items-baseline gap-2 min-w-0"> - <dt className="text-muted-foreground shrink-0 inline-flex items-center gap-1"> + <> + <dt className="text-muted-foreground inline-flex items-center gap-1.5 whitespace-nowrap"> <Clock className="h-3 w-3" aria-hidden /> - {label}: + {label} </dt> <dd className={ @@ -144,9 +154,10 @@ function TimeRow({ ? "text-foreground font-medium min-w-0 truncate" : "text-muted-foreground min-w-0 truncate" } + title={new Date(iso).toLocaleString()} > - {formatRelativeDate(iso)} + {formatted} </dd> - </div> + </> ); } diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index 713c5fd46..00b120d38 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -2,16 +2,26 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; -import { CornerDownLeftIcon, ExternalLink, Workflow } from "lucide-react"; +import { + AlertCircle, + Code, + CornerDownLeftIcon, + ExternalLink, + Pencil, + Workflow, +} from "lucide-react"; import Link from "next/link"; -import { useCallback, useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; +import { automationCreateRequest } from "@/contracts/types/automation.types"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; import { AutomationDraftPreview } from "./automation-draft-preview"; +const editArgsSchema = automationCreateRequest.omit({ search_space_id: true }); + // ---------------------------------------------------------------------------- // Result discrimination — mirrors the backend return shapes in // app/agents/multi_agent_chat/main_agent/tools/automation/create.py. @@ -62,12 +72,11 @@ function hasStatus(value: unknown, status: string): boolean { // ---------------------------------------------------------------------------- // Approval card — pending → processing → complete / rejected. // -// v1 deliberately supports only approve/reject. The drafted JSON is complex -// (full plan + triggers) and we already have a multi-turn refinement path via -// chat ("make it run at 10am instead" → the agent re-calls the tool with a -// refined intent). An in-card edit form would duplicate that flow and add UX -// surface area we don't need yet — leave it for the raw-JSON path on the -// detail page. +// Edit toggle reuses the same primitives as the Create-via-JSON page: raw +// textarea, Format, Zod validation against ``AutomationCreate`` (minus the +// ``search_space_id`` field, which the backend injects). Approve dispatches +// an ``edit`` decision with the parsed args when edits are pending, otherwise +// a plain ``approve``. Multi-turn chat refinement still works as a fallback. // ---------------------------------------------------------------------------- interface ApprovalCardProps { @@ -83,28 +92,34 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; const canApprove = allowedDecisions.includes("approve"); const canReject = allowedDecisions.includes("reject"); + const canEdit = allowedDecisions.includes("edit"); - const draft = useMemo(() => extractDraft(args), [args]); + const [pendingEdits, setPendingEdits] = useState<Record<string, unknown> | null>(null); + const [isEditing, setIsEditing] = useState(false); + + const effectiveArgs = pendingEdits ?? args; + const draft = useMemo(() => extractDraft(effectiveArgs), [effectiveArgs]); const handleApprove = useCallback(() => { - if (phase !== "pending" || !canApprove) return; + if (phase !== "pending" || !canApprove || isEditing) return; setProcessing(); onDecision({ - type: "approve", + type: pendingEdits ? "edit" : "approve", edited_action: { name: interruptData.action_requests[0]?.name ?? "create_automation", - args, + args: pendingEdits ?? args, }, }); - }, [phase, canApprove, setProcessing, onDecision, interruptData, args]); + }, [phase, canApprove, isEditing, setProcessing, onDecision, interruptData, args, pendingEdits]); const handleReject = useCallback(() => { - if (phase !== "pending" || !canReject) return; + if (phase !== "pending" || !canReject || isEditing) return; setRejected(); onDecision({ type: "reject", message: "User rejected the automation draft." }); - }, [phase, canReject, setRejected, onDecision]); + }, [phase, canReject, isEditing, setRejected, onDecision]); useEffect(() => { + if (isEditing) return; const handler = (e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) { handleApprove(); @@ -112,46 +127,77 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); - }, [handleApprove]); + }, [handleApprove, isEditing]); return ( <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 transition-[box-shadow] duration-300"> - <div className="flex items-start gap-3 px-5 pt-5 pb-4 select-none"> - <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> - <div className="min-w-0"> - <p className="text-sm font-semibold text-foreground"> - {phase === "rejected" - ? "Automation cancelled" - : phase === "processing" - ? "Saving automation" - : phase === "complete" - ? "Automation saved" - : "Create automation"} - </p> - {phase === "processing" ? ( - <TextShimmerLoader text="Saving automation" size="sm" /> - ) : phase === "complete" ? ( - <p className="text-xs text-muted-foreground mt-0.5"> - Automation created from this draft + <div className="flex items-start justify-between gap-3 px-5 pt-5 pb-4 select-none"> + <div className="flex items-start gap-3 min-w-0"> + <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> + <div className="min-w-0"> + <p className="text-sm font-semibold text-foreground"> + {phase === "rejected" + ? "Automation cancelled" + : phase === "processing" + ? "Saving automation" + : phase === "complete" + ? "Automation saved" + : "Create automation"} </p> - ) : phase === "rejected" ? ( - <p className="text-xs text-muted-foreground mt-0.5"> - No automation was saved — ask in chat to refine and try again. - </p> - ) : ( - <p className="text-xs text-muted-foreground mt-0.5"> - Review and approve to save. To change anything, reply in chat — I'll redraft. - </p> - )} + {phase === "processing" ? ( + <TextShimmerLoader + text={pendingEdits ? "Saving with your edits" : "Saving automation"} + size="sm" + /> + ) : phase === "complete" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + {pendingEdits + ? "Automation saved with your edits" + : "Automation created from this draft"} + </p> + ) : phase === "rejected" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + No automation was saved — ask in chat to refine and try again. + </p> + ) : ( + <p className="text-xs text-muted-foreground mt-0.5"> + {pendingEdits + ? "Showing your edits. Approve to save, or edit again." + : "Review and approve to save. Edit for fine-tuning, or reply in chat for a redraft."} + </p> + )} + </div> </div> + {phase === "pending" && canEdit && !isEditing && ( + <Button + size="sm" + variant="ghost" + className="rounded-lg text-muted-foreground -mt-1 -mr-2 shrink-0" + onClick={() => setIsEditing(true)} + > + <Pencil className="size-3.5" /> + Edit + </Button> + )} </div> <div className="mx-5 h-px bg-border/50" /> <div className="px-5 py-4"> - <AutomationDraftPreview draft={draft} raw={args} /> + {isEditing ? ( + <JsonEditor + initialValue={effectiveArgs} + onSave={(parsed) => { + setPendingEdits(parsed); + setIsEditing(false); + }} + onCancel={() => setIsEditing(false)} + /> + ) : ( + <AutomationDraftPreview draft={draft} raw={effectiveArgs} /> + )} </div> - {phase === "pending" && ( + {phase === "pending" && !isEditing && ( <> <div className="mx-5 h-px bg-border/50" /> <div className="px-5 py-4 flex items-center gap-2 select-none"> @@ -178,6 +224,85 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { ); } +interface JsonEditorProps { + initialValue: Record<string, unknown>; + onSave: (parsed: Record<string, unknown>) => void; + onCancel: () => void; +} + +function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { + const [text, setText] = useState(() => JSON.stringify(initialValue, null, 2)); + const [issues, setIssues] = useState<string[]>([]); + + function handleFormat() { + try { + setText(JSON.stringify(JSON.parse(text), null, 2)); + setIssues([]); + } catch (err) { + setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); + } + } + + function handleSave() { + setIssues([]); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch (err) { + setIssues([`Invalid JSON: ${(err as Error).message}`]); + return; + } + const result = editArgsSchema.safeParse(parsed); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + onSave(result.data as unknown as Record<string, unknown>); + } + + return ( + <div className="space-y-3"> + <textarea + value={text} + onChange={(e) => setText(e.target.value)} + spellCheck={false} + rows={16} + className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[12rem]" + aria-label="Automation JSON" + /> + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length} issue{issues.length === 1 ? "" : "s"} + </div> + <ul className="mt-1.5 space-y-0.5 text-xs text-destructive/90 list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue} className="font-mono"> + {issue} + </li> + ))} + </ul> + </div> + )} + <div className="flex items-center justify-end gap-2"> + <Button type="button" variant="ghost" size="sm" onClick={onCancel}> + Cancel + </Button> + <Button type="button" variant="outline" size="sm" onClick={handleFormat}> + <Code className="mr-1.5 h-3.5 w-3.5" /> + Format + </Button> + <Button type="button" size="sm" onClick={handleSave}> + Save edits + </Button> + </div> + </div> + ); +} + // ---------------------------------------------------------------------------- // Terminal result cards. // ---------------------------------------------------------------------------- diff --git a/surfsense_web/lib/format-date.ts b/surfsense_web/lib/format-date.ts index 9decd3402..c2f445537 100644 --- a/surfsense_web/lib/format-date.ts +++ b/surfsense_web/lib/format-date.ts @@ -1,4 +1,12 @@ -import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; +import { + differenceInDays, + differenceInMinutes, + format, + isThisYear, + isToday, + isTomorrow, + isYesterday, +} from "date-fns"; /** * Format a date string as a human-readable relative time @@ -23,6 +31,36 @@ export function formatRelativeDate(dateString: string): string { return format(date, "MMM d, yyyy"); } +/** + * Format a future date string as a human-readable countdown. + * - < 1 min: "Any moment" + * - < 60 min: "in 15m" + * - Today: "Today, 2:30 PM" + * - Tomorrow: "Tomorrow, 2:30 PM" + * - < 7 days: "in 3d" + * - This year: "May 30, 2:30 PM" + * - Older: "Jan 15, 2027" + * + * Mirrors {@link formatRelativeDate} but for moments strictly after now. + * Falls back to the past-relative formatter if the timestamp is not in + * the future (defensive — guards against stale "next_fire_at" values). + */ +export function formatRelativeFutureDate(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const minutesAhead = differenceInMinutes(date, now); + const daysAhead = differenceInDays(date, now); + + if (minutesAhead <= 0) return formatRelativeDate(dateString); + if (minutesAhead < 1) return "Any moment"; + if (minutesAhead < 60) return `in ${minutesAhead}m`; + if (isToday(date)) return `Today, ${format(date, "h:mm a")}`; + if (isTomorrow(date)) return `Tomorrow, ${format(date, "h:mm a")}`; + if (daysAhead < 7) return `in ${daysAhead}d`; + if (isThisYear(date)) return format(date, "MMM d, h:mm a"); + return format(date, "MMM d, yyyy"); +} + /** * Format a thread's last-updated timestamp for the chats sidebars. * Example: "Mar 23, 2026 at 4:30 PM" From b90bed2dbdfe7941f1a886481c45121309076501 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 15:38:57 +0200 Subject: [PATCH 077/133] chore: drop local design plan --- automation-design-plan.md | 1240 ------------------------------------- 1 file changed, 1240 deletions(-) delete mode 100644 automation-design-plan.md diff --git a/automation-design-plan.md b/automation-design-plan.md deleted file mode 100644 index db5f7a23c..000000000 --- a/automation-design-plan.md +++ /dev/null @@ -1,1240 +0,0 @@ -# SurfSense Automation Feature — Design Plan (v2) - -A generic, extensible automation system for SurfSense that lets users (and -future SurfSense features) trigger agent work on a schedule, on an external -event, or on demand — with the ability to author automations either by hand -or from a natural-language description that yields an editable, structured -definition. - -This document supersedes the v1 draft. It folds in the design audit pass and -the corrections from working through worked examples (notably: removing the -connector bias, clarifying the executor's role, integrating MCP cleanly, and -committing to JSON Schema as the single declarative language). - ---- - -## 1. The load-bearing principle - -> **The JSON definition is the program. Everything else is interpreter.** - -Every decision in this document serves that principle. If we ever face a -design choice and one option lets some behavior leak out of the definition -into the engine, we pick the other option. - -Three properties follow from this principle, and they're the reason the -system will survive feature growth: - -- **Reproducibility** — same definition + same inputs → same observable - behavior, regardless of which version of the engine runs it. -- **Portability** — definitions can be exported, imported, version- - controlled, code-reviewed, and shared across SurfSense instances. -- **LLM tractability** — the NL authoring flow works because the LLM only - needs to produce a self-contained JSON document that validates against a - schema. It doesn't need to understand the engine. - ---- - -## 2. The three-layer contract - -The system is structured as three layers. Layers 1 and 3 are defined by -SurfSense developers (at registration time). Layer 2 is what users write -(or the NL generator produces). The runtime reads all three to do its job. - -| Layer | What it is | Defined by | -| ----- | ---------- | ---------- | -| **1. Action contract** | Per-action params and output schema | Developers, at startup | -| **2. Automation definition** | One concrete saved automation | Users (or NL generator) | -| **3. Trigger contract** | Per-trigger params and payload schemas | Developers, at startup | - -Each layer constrains the next. The runtime reads all three but doesn't -know what's in them ahead of time. That's how a new action or trigger -type becomes available across the engine without code changes outside its -registration. - -A unification layer below Layer 1 — one catalog of "things this SurfSense -instance can do," shared by automations, agents, and future surfaces — was -considered and deferred (§3). v1 actions are stand-alone. - -### Schema language - -Every shape in every layer is described in **JSON Schema (draft 2020-12).** -No exceptions, no parallel languages, no inline shorthand. Two documented -extensions on top: - -- `default: "$some_token"` — runtime-resolved defaults. The vocabulary is - fixed: `$last_fired_at`, `$creator`, `$space_default`. The engine resolves - these to values before validation. -- `x-surfsense-*` annotations — editor hints (widget type, autocomplete - source). The validator ignores them; the form editor reads them. - ---- - -## 3. Capability unification layer — deferred to post-v1 - -Earlier drafts introduced a `Capability` registry as Layer 1: one catalog -of "things this SurfSense instance can do," shared by the automation -engine (as actions), the agent (as tools), and any future HTTP surface. -The motivation is real — one source of truth beats N parallel registries — -but v1 has a single action (`agent_task`) and a single consumer (the -automation engine). The five-field shape sketched earlier (`id`, -`description`, `input_schema`, `output_schema`, `handler`) cannot safely -host any non-trivial capability: it carries no caller identity, no -search-space scoping, and no authorization gate on tool delegation. -Building the abstraction with one consumer would lock in a shape that -doesn't survive the second consumer. - -The unification layer returns when the second consumer lands (Phase 2 -tight actions or Phase 4 MCP), redesigned from the start with: - -- A `CallContext` carrying caller user id, search space id, and run id, - passed to every handler invocation. -- Explicit scope declarations per capability (e.g. `reads:documents`, - `writes:slack`, `destructive`) for the authorization layer to read. -- A per-user, per-search-space filter consulted at both definition save - time (validating `agent_task.tools`) and run time (scoping the agent's - tool list to what the automation creator can delegate). - -Until then: - -- v1 actions are stand-alone units (Layer 1 below); the automation engine - reads its own action registry, nothing else. -- `agent_task.params.tools` is a forward-looking allowlist field with no - v1 semantics beyond "list of string identifiers." The handler's tool - resolution is opaque to the automation contract. - -### Credentials — deferred to Phase 2 - -External-credential handlers (Slack, email, etc.) require per-user or -per-connection auth. v1 actions run server-side with app-level -configuration. When tight actions ship in Phase 2, the credential design -lands as part of the unification redesign: connection IDs in the -definition (never tokens); credentials loaded per-call by the handler -context (never pre-loaded into worker memory); credentials never enter -LLM context. - -### MCP — deferred to Phase 4 - -External tool servers feeding tools into a shared registry land with the -rest of the integration tooling in Phase 4, after the unification layer -is in place. The two-tier registry, `mcp_connections` and `mcp_tools` -tables, and the harvester arrive as a single coherent step then. - ---- - -## 4. Action contract - -An `Action` is what a user references in a plan step. Some actions are -deterministic single-purpose handlers (`slack_post`, `send_email`); one -action (`agent_task`) hosts an LLM and a tool allowlist for cases where -judgment is needed. The contract is the same in both cases — only the -handler differs. - -```python -@dataclass(frozen=True, slots=True) -class ActionDefinition: - type: str # "agent_task", "slack_post" - name: str # short UI label - description: str # for the NL generator and the UI - params_schema: dict # JSON Schema for step.params - handler: ActionHandler -``` - -This is the v1 shape: five fields, no handler context, no output -contract, no artifact declaration. The deferrals are intentional: - -- **`output_contract`** — Phase 2. Deterministic handlers will return - a fixed shape; v1's only action (`agent_task`) takes an - `output_schema` inside `params` and validates against that instead. -- **`produces_artifacts`** — Phase 5. Artifact lifecycle (storage, - signed URLs, retention) is its own design step; v1 handlers - persist their own outputs. -- **Handler context** — paired with the unification redesign (§3). - v1 handlers receive `(args)` only; per-user / per-search-space - behavior is not yet a v1 concern. - -### Tight vs loose actions - -Two patterns coexist by design: - -- **Tight actions** (`slack_post`, `linear_create_issue`, - `send_email`) — deterministic single-purpose handlers. ~20 LOC - each. **Phase 2.** -- **Loose actions** (`agent_task`) — params_schema accepts a `prompt`, - a `tools` allowlist, and an optional `output_schema` declaring what - the agent must return; the handler validates the agent's output - against it. **v1.** - -The agent's `tools` allowlist resolves opaquely in v1; the redesigned -unification layer (§3) will give both invocation modes access to the -same vocabulary, with per-user authorization gating both. - -### How names in the definition become function calls - -The definition contains strings like `"action": "agent_task"`. The -string is just a name — it does not point to a function. At runtime, -the executor performs a **name-based lookup** against the action -registry: - -```python -action_def = action_registry.get(step.action) # dict lookup -handler = action_def.handler # Python callable -result = await handler(resolved_params) # invocation -``` - -The registry is a Python dict populated at process startup. Each entry -in `automations/registries/actions/*.py` calls `register_action(...)` -at module import time, putting its `ActionDefinition` (including the -handler function reference) into the registry. - -The definition is pure data. The registry is the engine's runtime -vocabulary. They meet at name-based lookup; nothing else crosses the -boundary. - -### The full expressive spectrum - -The contract supports a continuous spectrum from purely deterministic to -fully agentic. Six practical shapes worth recognizing: - -| Shape | Example | Cost / latency profile | -| --- | --- | --- | -| **1. Direct call** | `slack_post` with literal channel and template | No LLM. ~200ms. Fractions of a cent. | -| **2. Direct call with computed inputs** | `linear_create_issue` using `{{summary.title}}` from a prior step | No LLM for this step. Cheap. | -| **3. Single-domain agent task** | `agent_task` with `tools: ["slack.*"]` only | One LLM, bounded toolset. | -| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named tools. | -| **5. Multi-domain agent task, broad** | `agent_task` with `tools: ["slack.*", "github.*", "linear.*"]` | One LLM, large toolset, most agentic. | -| **6. Composed plan** | `agent_task` (narrow) for thinking → `slack_post` + `linear_create_issue` for acting | Best cost-to-power ratio. | - -Shape 6 is the underrated one and the cost-and-speed answer. The agent -reasons once (Shape 3 or 4) and its structured output drives several -deterministic actions. This is roughly 5–10x cheaper and 3–4x faster than -forcing the agent to do everything (Shape 5) and produces the same outcome. - -**The NL generator's job is to propose Shape 6-style plans by default.** -The Review LLM flags proposals that use `agent_task` for steps a -deterministic action could handle. This is the discipline that keeps -automations cheap at scale. - -The user navigates the spectrum by intent (describing what they want), not -by mechanism — the shape selection is the engine's responsibility, not the -user's. - ---- - -## 5. Automation definition - -This is the JSON the user writes (or the NL generator produces). Stored in -`automations.definition` as JSONB. - -### Top-level shape - -```jsonc -{ - "schema_version": "1.0", - "name": "Daily competitor digest", - "goal": "Summarize new competitor content and post to Slack", - - "inputs": { - "schema": { - "type": "object", - "required": ["since"], - "properties": { - "since": { "type": "string", "format": "date-time", - "default": "$last_fired_at" }, - "tags": { "type": "array", "items": { "type": "string" }, - "default": ["competitor"] } - } - } - }, - - "triggers": [ - { - "type": "schedule", - "params": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } - } - ], - - "plan": [ - { - "step_id": "research", - "action": "agent_task", - "params": { - "prompt": "Find documents tagged {{inputs.tags}} indexed since {{inputs.since}}. Return JSON with bullets and source_doc_ids.", - "tools": ["search_space.query", "search_space.fetch_document"], - "model": "anthropic/claude-sonnet-4-7", - "output_schema": { - "type": "object", - "required": ["bullets", "source_doc_ids"], - "properties": { - "bullets": { "type": "array", "items": { "type": "string" } }, - "source_doc_ids": { "type": "array", "items": { "type": "string" } } - } - } - }, - "output_as": "summary" - }, - { - "step_id": "deliver", - "action": "slack_post", - "params": { - "channel_id": "C0123", - "message_template": "*Competitor digest*\n\n{% for b in summary.bullets %}• {{b}}\n{% endfor %}" - } - } - ], - - "execution": { - "timeout_seconds": 600, - "max_retries": 2, - "retry_backoff": "exponential", - "concurrency": "drop_if_running", - "on_failure": [ /* steps to run if main plan fails after retries */ ] - }, - - "metadata": { "tags": ["digest"] } -} -``` - -### Plan steps - -```jsonc -{ - "step_id": "...", // unique within plan - "action": "...", // references an ActionDefinition.type - "when": "{{ ... }}", // optional Jinja expr → bool; false = skip - "params": { ... }, // validated against action's params_schema - "output_as": "...", // binds output to this name for later steps - "max_retries": 0, // optional, overrides automation default - "timeout_seconds": 1200 // optional, overrides automation default -} -``` - -Steps run **sequentially**. No parallelism, no DAGs, no loops. If a user -needs branching, they use `when:` on multiple steps. If they need -parallelism or iteration, they use `agent_task` and let the agent reason -about it, or they compose automations through events (§7.5). - ---- - -## 6. Trigger contract - -Three trigger types. That's the entire taxonomy. - -### `schedule` - -```python -TriggerDefinition( - type="schedule", - params_model=ScheduleTriggerParams, # cron + timezone -) -# At fire time the schedule producer emits runtime inputs -# (fired_at, scheduled_for, last_fired_at) which are merged with the -# trigger row's static_inputs (static wins) and validated against -# automation.definition.inputs.schema_. -``` - -Implementation: extends `app/utils/periodic_scheduler.py`, which already -reads connector sync schedules. Adds a second source — `automation_triggers -WHERE type='schedule'`. Same Celery Beat checker, two source tables. - -Minimum interval: 1 minute (the existing checker's resolution). The form -editor warns when users set intervals under 15 minutes that they probably -want an event trigger instead. - -### `webhook` - -```python -TriggerDefinition( - type="webhook", - params_schema={ - "type": "object", - "properties": { - "input_mapping": { - "type": "object", - "additionalProperties": { "type": "string" } - # values are JSONPath expressions - } - } - }, - # payload is whatever the POST body is; user-defined shape via mapping -) -``` - -Endpoint: `POST /api/v1/automations/{id}/fire`. Bearer token shown once, -hashed at rest, rotatable, revocable. Returns `202 Accepted` with the -created run's URL. Caller polls for status; we do not push callbacks in -v1 (a `callback_webhook` action can be added later). - -Idempotency: honors `Idempotency-Key` header or `idempotency_key` in body. -Dedups against runs in the last 24 hours. - -### `event` - -```python -TriggerDefinition( - type="event", - params_schema={ - "type": "object", - "required": ["event_type"], - "properties": { - "event_type": { "type": "string" }, # e.g. "drive.file_added" - # or "surfsense.podcast.generated" - "filters": { "$ref": "#/definitions/filter_expression" } - } - } - # payload shape is documented per event_type in a separate registry -) -``` - -**Events absorb both connector events and internal SurfSense events.** A -file added to Drive and a podcast finishing in SurfSense are both events -in the same `domain_events` table, both subscribable by automations, both -matched by the same dispatcher code. The engine doesn't distinguish. - -### Filter grammar - -Filters are JSON-structured operators, not expressions. This is the one -place we deliberately don't use Jinja, because filters run on a hot path -(every event matched against every subscribing trigger) and structured -filters can be indexed and short-circuited. - -Vocabulary: -- Equality: `equals`, `not_equals` -- String: `starts_with`, `ends_with`, `contains`, `regex` -- Numeric: `gt`, `gte`, `lt`, `lte` -- Set: `in`, `not_in` -- Existence: `exists` -- Composition: `$and`, `$or`, `$not` - -Inspired by AWS EventBridge and MongoDB query syntax. The filter grammar -itself is published as a JSON Schema, so users get inline error messages. - ---- - -## 7. Runtime components - -Each component is distinct, replaceable, and has one job. - -### 7.1 Dispatcher - -What it does: matches firing triggers to automations, creates `AutomationRun` -rows, enqueues executor tasks. - -For schedule triggers: Celery Beat polls the trigger table, computes due -ones, fires. - -For webhook triggers: the FastAPI handler is the dispatcher entry point. -Validates token, runs input_mapping, creates run. - -For event triggers: subscribes to the `domain_events` table. For each new -event, evaluates all matching triggers' filters, fires the matches. - -Common path (after a trigger has fired): -1. Resolve `inputs` from trigger payload and defaults -2. Validate resolved inputs against the automation's input schema -3. **Idempotency check** — dedup against existing pending/running runs -4. **Snapshot the resolved definition** into the run row (immutable history) -5. Enqueue executor task on the single `automations_default` Celery queue - -The cost-estimate pre-check (originally step 3) is **deferred**. v1 -actions do not declare cost estimates, the run row has no `cost_usd` -column, and no handler reports tokens used — so neither pre-flight -prediction nor mid-flight accumulation can be enforced. `Execution` -therefore does not expose `budget_cap_usd` in v1; it returns as a single -field addition the day the cost ledger ships (per-action cost reporting -+ `automation_runs.cost_usd` column + executor accumulation). - -Queue routing by `expected_duration_seconds` is **deferred** until load -patterns justify a second queue. v1 uses a single queue. - -### 7.2 Executor - -What it is: **a Celery task wrapping a single function that walks a plan -step by step.** Not an agent, not a workflow engine, not a scheduler. A -loop with bookkeeping. Maybe 200 lines. - -```python -async def execute_run(run_id: int) -> None: - run = load_run(run_id); run.status = "running"; save(run) - context = build_run_context(run) - step_outputs = {} - - for step in run.plan: - if step.when and not evaluate_predicate(step.when, context | step_outputs): - record_step_skipped(run, step); continue - - resolved_params = render_params(step.params, context | step_outputs) - action = action_registry.get(step.action) - validate(resolved_params, action.params_schema) - - try: - result = await with_retries( - action.handler, - ctx=build_action_context(run, action), - args=resolved_params, - policy=step.retry_policy or run.execution.retry_policy, - ) - validate(result, step.output_schema) - if step.output_as: - step_outputs[step.output_as] = result - record_step_succeeded(run, step, result) - except Exception as e: - record_step_failed(run, step, e) - await run_on_failure(run, e) - return - - run.status = "succeeded"; save(run) - publish_event("automation.run.succeeded", run) # see §7.5 -``` - -Intelligence lives **inside handlers**, not in the executor. The most -intelligent handler is `agent_task`, which spins up a LangGraph Deep Agent -for one step and returns when the agent finishes. The executor sees a -validated dict come back; it doesn't know that step was "smart." - -### 7.3 Action handlers - -One handler per `ActionDefinition.type`. Receives the validated `args` -dict and returns whatever the step's output validates against (a fixed -shape declared by tight actions, or a dynamic shape declared via -`output_schema` in the step params for `agent_task`). - -Handlers do not know about retries or timeouts — those are the -executor's concern. - -In v1, handlers take `(args)` only. The `CallContext` parameter sketched -in §7.2's pseudo-code (caller user id, search space id, run id, -credential resolver) arrives with the unification layer redesign (§3); -v1's single action (`agent_task`) reads what it needs from app-level -configuration. - -### 7.4 Template engine - -#### Why it exists - -Most fields in an automation definition contain literal strings the user -authored once — but the actual rendered value has to change per run, because -it includes data from the trigger payload or from prior step outputs. The -template engine is what turns `"Daily digest for {{run.started_at}}"` into -`"Daily digest for 2026-05-26"` at run time. - -Three fields use it: -- `*_template` strings in tight action configs (Slack messages, email bodies, - Linear titles, etc.) -- `prompt` in `agent_task` configs (so the agent sees resolved values, not - `{{...}}` placeholders) -- `when:` step predicates (which need to evaluate to a boolean) - -#### Public interface - -Single module, ~80 lines. Three public functions — everything else in the -engine routes through these: - -```python -def render_template(template: str, context: dict) -> str: ... -def evaluate_predicate(expression: str, context: dict) -> bool: ... -def build_run_context(run, step_outputs) -> dict: ... -``` - -Backed by Jinja2's `SandboxedEnvironment`. The whole module is the seam: if -the template language is ever swapped, only this file changes. - -#### Security architecture: allowlist by default - -`SandboxedEnvironment` starts empty. A freshly-created instance gives a -template access to: -- Variables in the context dict we pass in (`run`, `inputs`, prior step - outputs) -- Public (non-underscore) attributes of those variables -- Jinja's built-in control flow (`{% if %}`, `{% for %}`, `{% set %}`) - -Nothing else. No Python builtins, no modules, no I/O, no network, no -filesystem. Everything beyond the above must be **explicitly registered.** -This is the structurally important property: anything we didn't add is -inaccessible. The risk surface equals the size of what we registered. - -The three sandbox rules that enforce this: -1. **Attribute access is filtered** — names starting with underscore are - rejected. This blocks the entire family of `{{x.__class__.__mro__...}}` - Python escape paths in one rule. -2. **Globals are allowlist-only** — `open`, `eval`, `exec`, `__import__`, - `getattr`, every module name, are all absent unless we register them. - We register zero globals. -3. **Unsafe callables are blocked** — `str.format` and `str.format_map` - specifically (due to CVE-2016-10745), plus anything marked - `unsafe_callable`. - -#### What we register, exactly - -- **Filters: a curated 15**, no more. `join`, `length`, `default`, `upper`, - `lower`, `truncate`, `tojson`, `date`, `replace`, `trim`, `slugify`, - `first`, `last`, `sort`, `reverse`. Each one is audited for what it does - with its input; none of them takes a callable, runs `eval`, or reaches - into Python objects beyond simple data transformation. -- **Globals: none.** -- **Tests: only the safe built-ins** (`defined`, `none`, `number`, `string`, - `mapping`, `sequence`, `boolean`). - -Adding a new filter requires a deliberate code change and review: does this -filter do anything dangerous with its input? If yes, don't add it. The list -only grows by audited additions. - -#### Runtime limits (defense in depth) - -The sandbox handles the attack surface inside the template language. Three -additional limits handle resource exhaustion that the language permits but -the runtime shouldn't tolerate: - -- **Template source length capped at 8 KB.** Checked before parsing. -- **Render time capped at 100 ms per render.** Implemented via a watchdog - thread; renders that exceed are killed and the step fails. Catches - `{% for i in range(10**9) %}` and nested loop bombs. -- **Output size capped at 1 MB.** A small template can produce a multi-GB - string via `{{ 'A' * 10**8 }}`-style multiplication; this catches it. - -Plus `StrictUndefined`: any reference to a missing variable raises -immediately rather than silently rendering empty, so misconfigurations -fail fast. - -#### Threat model and residual risk - -The trust model from day one is: - -- Templates are generated by an LLM from a user's natural-language input - (see §10), or written/edited by humans in the editable form -- A second LLM reviews the proposal and produces a plain-language summary - plus flagged anomalies for the user -- The user reviews and approves before the automation runs -- The Generator LLM's input is scoped (user prompt + schema + registry - only — no arbitrary document content), minimizing prompt-injection paths - -The sandbox + runtime limits + curated filter list protect against the -malformed-template attack. Human review protects against the -semantically-malicious-but-syntactically-valid attack. These are -complementary layers, not redundant. - -Known residual risks, each genuinely small: - -- **Future Jinja CVEs.** Historical sandbox bypasses have existed and - been patched. This is a generic third-party-dependency risk, comparable - to bugs in any other library we rely on. Mitigation: subscribe to - security advisories, ship updates within a week of disclosure. -- **Side channels via prompts to LLMs.** A template that renders into a - prompt can attempt prompt injection of the agent at run time. This is - not a sandbox concern but a separate concern in `agent_task`'s design. -- **Operator deployments with long-lived secrets in worker env vars.** - Mitigation: credentials fetched per-handler-per-call via - `ActionContext.resolve_credentials`, never pre-loaded into worker - env vars accessible to templates. - -The sandbox-with-allowlist architecture means **the attack surface -equals the set of things we registered.** With zero globals registered -and 15 audited filters, the surface is small, bounded, and reviewable. -This is the structural property that makes the architecture sound, and -it doesn't depend on hypothetical assumptions about who authors templates. - -#### Pre-Phase-5 gate - -One trust-model change is documented in the roadmap: **Phase 5 introduces -template sharing across SearchSpaces** (automation templates as -exportable, importable artifacts). At that point, the *approver* of a -template (the original author) is no longer the *runner* (the importer). -The "human reviews before save" mitigation breaks down because the -reviewer doesn't bear the risk. - -Before Phase 5 ships, this needs an explicit re-approval flow: importing -a template triggers a fresh review pass by the importing user, with the -flagged-anomalies output prominently displayed, and the import cannot -complete without explicit per-template approval. - -This is a UX/flow decision, not a template-language migration. Jinja -itself stays; what changes is the approval workflow at the import boundary. - -#### The `run.*` namespace exposed in every template - -``` -run.id, run.started_at, run.automation_id, run.automation_name, -run.automation_version, run.trigger_type, run.trigger_id, -run.search_space_id, run.creator_id, run.attempt, -run.failed_step_id, run.error.* (only in on_failure context) -``` - -#### Default value rendering - -Non-string template values render as JSON by default (via the `finalize` -hook): lists become `["a", "b"]`, dicts become `{"k": "v"}`, datetimes -become ISO 8601. The `| join`, `| length`, `| tojson` filters give explicit -control. Strings render as themselves with no quoting. `None` renders as -empty string in templates, as `null` in JSON contexts. - -### 7.5 Event bus - -`domain_events` table, polled by Celery Beat alongside the existing -scheduler. Both connector events and internal SurfSense events publish to -it. Both are consumed by the dispatcher's event-trigger subscriber. - -**Automations themselves publish events.** Successful and failed runs emit -`automation.run.succeeded` / `automation.run.failed` events with the run -metadata. This makes automations composable through events — chain them by -subscribing one automation's event trigger to another's run event. No new -mechanism; the trigger filter and event publishing already exist. - -Upgrade path documented: when throughput or latency demands it, replace -PostgreSQL polling with Redis Streams. The `events.publish()` and -`events.subscribe()` interfaces stay the same. Nothing else changes. - ---- - -## 8. Cross-cutting concerns - -### Concurrency policy - -Per-automation `concurrency` field controls what happens when a new fire -occurs while a previous run is still running: - -- `drop_if_running` — silently skip the new fire -- `queue` — execute serially, in arrival order -- `allow_parallel` — start a new run independently - -The dispatcher enforces this before enqueueing. - -### Retry policy - -Three fields, per-automation defaults with optional per-step overrides: -- `max_retries`: integer, 0–10 -- `retry_backoff`: `none` | `linear` | `exponential` -- `timeout_seconds`: integer - -Retries on: -- Action handler exceptions -- Output schema validation failures (for dynamic-output actions, the - validation error is fed back to the LLM in the retry) - -Not retries: -- `when:` evaluation failures (these are user errors, surface immediately) -- Input validation failures (caught at dispatch, never reach the executor) - -### Budget enforcement *(deferred — not in v1)* - -Future shape: `budget_cap_usd` on `Execution`, dispatcher refuses to -enqueue if estimated cost exceeds it, executor kills the run if -accumulated cost crosses it mid-flight (the LLM ops handler reports -tokens consumed back to the executor between calls). - -Prerequisites before this can land: -- Each action declares cost reporting (tokens × model price, API call - charges) — `ActionDefinition` has no such field today. -- `automation_runs.cost_usd` column + executor accumulates per step. -- A historical-cost ledger so pre-flight estimation can return useful - numbers (otherwise the dispatcher gate is guessing). - -Until all three exist, v1 has no surface for budget enforcement. - -### On-failure handlers - -`execution.on_failure` is a list of steps that run after the main plan has -failed and all retries are exhausted. Same step shape as the main plan. -Cannot have their own `on_failure`. See `run.error.*` in the run context. - -### Artifacts - -Actions that produce artifacts declare `produces_artifacts: list[ArtifactSpec]`: - -```python -@dataclass -class ArtifactSpec: - kind: str # "audio", "document", "image", "data" - retention: str # "transient" | "default" | "permanent" - visibility: str # "private" | "search_space" | "shared" -``` - -The engine handles storage (writes to SurfSense's existing object storage), -URL generation (signed, scoped to the run's permissions), and cleanup (a -nightly Celery Beat task deletes expired artifacts). - -### Duration classes and queue routing — deferred - -The original design routed runs to multiple Celery queues based on each -action's declared `expected_duration_seconds`. v1 ships with **one -queue** (`automations_default`) and actions do not declare a duration. -Multi-queue routing returns when burst load on a single queue actually -justifies the operational complexity of independent worker pools. - -Adding the second queue is a config change plus reintroducing -`expected_duration_seconds` on the `ActionDefinition` dataclass — both -mechanical, additive, and free of design rewrite. - ---- - -## 9. Data model - -**v1 ships three tables:** `automations`, `automation_triggers`, -`automation_runs`. All scoped by `search_space_id` for RBAC. - -The other three tables described in earlier drafts are deferred: - -- `domain_events` → **deferred to Phase 3** (introduced with the event - trigger). -- `mcp_connections`, `mcp_tools` → **deferred to Phase 4** (MCP - integration). - -The deferred tables ship as-is when their consuming feature lands; -nothing in the v1 schema needs to change to accommodate them. The three -v1 tables form the engine's persistent state — definitions, triggers, -and an immutable run history. - -### `automations` - -| field | type | notes | -| ----------------- | ----------------------------------- | -------------------------------------------------------------------------- | -| `id` | int PK | | -| `search_space_id` | FK → `search_spaces.id` | | -| `created_by` | FK → `users.id` | runs execute as this identity | -| `name` | str | | -| `description` | str | | -| `status` | enum | `active`, `paused`, `archived` | -| `definition` | jsonb | the editable structured spec | -| `version` | int | bumped on every edit | -| `created_at` / `updated_at` | timestamps | | - -### `automation_triggers` - -| field | type | notes | -| --------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------- | -| `id` | int PK | | -| `automation_id` | FK | | -| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | -| `params` | jsonb | trigger-type config, validated against trigger's `params_schema` | -| `static_inputs` | jsonb | per-attachment domain values merged into every run (static wins on collision) | -| `enabled` | bool | | -| `last_fired_at` | timestamp | | -| `next_fire_at` | timestamp / null | precomputed next fire moment for schedule triggers | - -`secret_hash` (for webhook bearer tokens) is **deferred to Phase 2** with -the webhook trigger. - -### `automation_runs` - -| field | type | notes | -| ----------------- | ---------------------------------------------------------------------------- | -------------------------------------------------- | -| `id` | int PK | | -| `automation_id` | FK | | -| `trigger_id` | FK / null | null = manual via UI | -| `status` | enum | `pending`, `running`, `succeeded`, `failed`, `cancelled`, `timed_out` | -| `definition_snapshot` | jsonb | the definition as it was when this run fired | -| `inputs` | jsonb | merged & validated inputs (trigger.static_inputs ∪ producer runtime data, static wins) | -| `step_results` | jsonb | array of per-step results with timing | -| `output` | jsonb / null | | -| `artifacts` | jsonb | references to created artifacts | -| `error` | jsonb / null | | -| `started_at` / `finished_at` | timestamps | | -| `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | - -`cost_usd` (per-run accumulated cost) is **deferred** until at least one -action records token-level cost. When reintroduced it lands as a -column-only migration. - -### Deferred tables - -- **`domain_events`** — the event bus backing event triggers. Ships in - Phase 3 with the event trigger. v1 only emits `automation.run.*` - events into application logs; the table is added when at least one - consumer needs to subscribe to them. -- **`mcp_connections`** / **`mcp_tools`** — see §3. Both ship in Phase 4 - alongside the MCP harvester and the two-tier registry. - -NL drafts are **not** a core table. They live in a generic short-TTL -store (Redis or a transient table) when the NL flow is built in -Phase 3. - ---- - -## 10. NL authoring flow - -**This is how the system is intended to be used from day one, not just a -Phase 3 addition.** The product surface is: user describes intent in natural -language, LLM produces a structured proposal, user reviews and edits in an -auto-generated form, then saves. Hand-authoring JSON directly is supported -but is not the primary path. - -This shapes the trust model. Templates are LLM-generated from day one, not -hand-written by power users. The mitigation is human-in-the-loop review, -not "trusted authors only." - -### Pass 1: Proposal generation - -User provides natural-language input. The Generator LLM is given: -- The full schema set (input schema for definition, registry of action - types with their params_schemas, registry of trigger types, list of - allowed Jinja filters) -- A tool to list available connectors, channels, and other SearchSpace - resources, so it doesn't invent names that don't exist -- A few-shot set of examples - -**Scoped input.** The Generator does *not* receive arbitrary SearchSpace -document content. Its context is the user's prompt plus the schema and -registry information. This minimizes the prompt-injection surface — there's -no document text in the context for an attacker to seed instructions into. - -If a user wants document-aware generation later ("create an automation -that processes documents like this one"), that's a deliberate feature -extension with its own prompt-injection mitigations, not the default flow. - -Output: a structured proposal matching the automation definition schema. - -### Pass 2: Deterministic validation - -Server-side, before the proposal reaches the user: -- Validate against JSON Schema (shape correctness) -- Verify every action and trigger type referenced exists in the registry -- Verify every connector/channel/resource referenced exists in this SearchSpace -- Validate every template against the sandbox's allowlist (no underscore - attributes, no unregistered filter names, length under cap) - -Failures here are deterministic errors, not warnings. A proposal that -references a non-existent action or includes a template using -`{{x.__class__}}` is rejected before the user sees it; the Generator is -re-prompted with the validation error and asked to fix the proposal. - -### Pass 2.5: Review pass - -A second LLM call — the **Review LLM** — examines the validated proposal and -produces two outputs for the user: - -1. **A plain-language summary** of what the automation will do, in business - terms. "This automation will run every weekday at 9am. It reads documents - in this SearchSpace tagged 'competitor' that were indexed since the last - run, asks an agent to summarize them as 5 bullets, and posts the summary - to your #engineering-standup Slack channel. Estimated cost: $0.40 per - run." - -2. **A "things worth checking" list** flagging anything unusual: - - Templates with unusual attribute paths or filter usage - - Prompts containing instructions that look more like commands than - descriptions ("ignore previous instructions" style) - - Action sequences that touch external systems without obvious benefit - to the user - - Cost estimates that seem high relative to the goal - - References to actions the user hasn't used before - - Schedules tighter than 15 minutes (likely should be event triggers) - -The Review LLM is a **UX layer** that makes review actually useful. It is -**not a security boundary.** The deterministic controls (sandbox, runtime -limits, schema validator) are the security boundaries. The Review LLM -helps users catch their own intent mismatches and surfaces anomalies for -attention, but the sandbox would block dangerous templates even if the -Review LLM missed them. - -This separation is important: two probabilistic controls compounding can -create a false sense of security. The Review LLM is explicitly framed in -the architecture as helper, not gatekeeper. - -### Pass 3: Editable review - -The user lands on a form pre-filled with the proposal. The page shows: -- The plain-language summary from the Review pass -- The flagged items, prominently displayed near the relevant fields -- The full editable form, auto-generated from the JSON Schemas -- Cost estimate and impact summary (which external systems get touched) - -**Every field is editable.** Clarifications appear as required fields. -Templates are shown in code-styled fields with syntax highlighting and the -filter palette visible. The user can edit any field; saving re-runs Pass 2 -(deterministic validation) before persisting. - -Hitting **Save** promotes the proposal to an `automation` row. - -### Editing existing automations - -NL editing of an existing automation is a patch operation: the Generator -LLM receives the current definition plus the NL instruction and produces a -modified proposal. The same Pass 2 (validation) and Pass 2.5 (review) run -against the modified version, and the user reviews the diff before saving. -Existing run history is unaffected — only future runs use the new version. - -### Why human-in-the-loop is non-negotiable - -The Generator LLM, the Review LLM, and the sandbox are three layers of -defense against malformed or malicious proposals. The human approval step -is the fourth and most important layer. It exists because: - -- LLMs can be prompt-injected; humans can spot text that asks them to - ignore instructions -- LLMs can produce confident-but-wrong proposals; humans can catch - semantic mismatches between intent and output -- The cost of a bad automation running unattended is high; the cost of a - user clicking "approve" after reading is low - -The architecture must never offer "auto-approve" or "skip review" options -for LLM-generated proposals. Save requires human action on the proposal, -always. - ---- - -## 11. Repository layout - -``` -surfsense_backend/app/ -├── automations/ # NEW: the engine -│ ├── __init__.py -│ ├── persistence/ # SQLAlchemy models + enums for 3 tables -│ ├── schemas/ # Pydantic schemas (definition envelope, etc.) -│ ├── routes.py # FastAPI router (/api/v1/automations) -│ ├── service.py # CRUD + business logic -│ ├── dispatcher.py # trigger matching, run creation -│ ├── executor.py # the Celery task that runs a plan -│ ├── templating.py # Jinja sandbox + filters -│ ├── events.py # publish/subscribe for domain_events -│ ├── filters.py # JSON filter grammar evaluator -│ ├── registries/ # action and trigger registries -│ │ ├── actions/ # ActionDefinition + handler registration -│ │ └── triggers/ # TriggerDefinition -│ └── nl/ # Phase 1 — primary user path -│ ├── generator.py # Generator LLM -│ ├── reviewer.py # Review LLM (summary + flagged items) -│ ├── validator.py # deterministic schema + resource checks -│ └── prompts.py # system prompts for both LLMs -│ -├── utils/ -│ └── periodic_scheduler.py # EXTENDED to scan automation_triggers -│ -└── alembic/versions/ - └── NN_add_automation_tables.py - -surfsense_web/app/(routes)/ -└── automations/ # NEW: UI - ├── page.tsx # list - ├── new/page.tsx # NL input + draft preview (Phase 1) - ├── [id]/page.tsx # editor (auto-generated forms) - └── [id]/runs/page.tsx # run history, streamed via Electric SQL -``` - ---- - -## 12. Phased delivery - -Each phase delivers something usable. Each de-risks the next. **NL authoring -is the primary user path from Phase 1** — what evolves across phases is -which actions and triggers are available, not whether users can describe -automations in natural language. - -### Phase 1 — Engine MVP with NL authoring - -**Step 1 (current scope, this batch of commits):** -- 3 tables (`automations`, `automation_triggers`, `automation_runs`) + - Alembic migration -- Empty action and trigger registries under - `app/automations/registries/` (concrete entries land in later steps) -- Pydantic schemas for the automation definition envelope, the two v1 - trigger params shapes (`schedule`, `manual`), and the one v1 action - params shape (`agent_task`) -- Module structure under `app/automations/` (persistence/, schemas/, - registries/), fully isolated from the existing codebase - -**Step 2:** -- The `agent_task` action handler and the `schedule` / `manual` triggers - registered in `app/automations/registries/`. Tool resolution for - `agent_task.params.tools` is opaque to the contract — the handler - decides what string identifiers it accepts and how they resolve. - -**Step 3:** -- Executor (single-queue Celery task) with retries and timeouts -- Template engine (Jinja sandbox + the v1 filter allowlist + runtime - limits) -- Manual "Run now" endpoint - -**Step 4:** -- NL authoring flow: Generator LLM, deterministic validator, Review LLM, - editable form -- Run history UI with Electric SQL streaming - -**After Phase 1**: a user can describe an automation in natural language, -review the proposal (with summary + flagged anomalies), edit any field, -save, and watch it run on a schedule. - -### Phase 2 — Webhooks and delivery -- `webhook` trigger with per-automation bearer tokens -- Tight actions: `slack_post`, `send_email`, `notification` -- `transform_data` action -- `on_failure` hooks -- Step-level retry/timeout overrides -- Concurrency policy enforcement - -**After Phase 2**: external systems can drive automations, results go -somewhere humans see, complex pipelines have proper error handling. - -### Phase 3 — NL authoring polish -- NL patch flow for editing existing automations (diff-based) -- Conversational refinement during proposal review ("change the schedule - to weekdays only," "add a Slack notification on failure") -- Improved Review LLM coverage (more anomaly patterns, cost-relative-to- - goal heuristics) -- Saved prompt templates and starter examples - -**After Phase 3**: NL authoring is the polished primary surface; edit -flows are conversational rather than form-only. - -### Phase 4 — Event triggers + integration tooling -- `domain_events` table and `events.py` module -- Indexing pipeline publishes `connector.*` events (smallest change — just - add publish calls to the existing flow) -- Automations publish `automation.run.*` events on completion -- `event` trigger with filter grammar -- The unification layer redesign (see §3) — `CallContext`, scope - declarations, per-user authorization gating -- MCP integration on top of the unification layer (external tool servers - harvested into the shared catalog) - -**After Phase 4**: "do X when Y happens" automations work, including -automation-chaining through events; external MCP tools and SurfSense -actions share one vocabulary. - -### Phase 5 — Wrapping existing features and sharing -- Wrap existing SurfSense features as actions: `podcast_generation`, - `report_generation`, `indexing_sweep` -- Artifact lifecycle implementation -- `expected_duration_seconds` based queue routing (split `automations_long` - from `automations_default`) -- **Automation templates** (shareable, exportable, importable) — with - the import re-approval flow that handles the approver-≠-runner trust - shift documented in §7.4's pre-Phase-5 gate -- Cross-automation composition examples in the docs - -**After Phase 5**: every existing SurfSense feature is automatable -without any per-feature code, and automations can be shared between -SearchSpaces and users. - ---- - -## 13. Decisions locked - -For reference — every decision made through the design process, in one -place. - -### Foundations -1. ✅ JSON Schema (draft 2020-12) is the single schema language for everything -2. ✅ Definition is the program; infrastructure is the interpreter -3. ✅ List of steps (not single action) in the plan, with `output_as` chaining -4. ⏸ Capability unification layer (one catalog shared by automations, agents, and future surfaces) — **deferred to post-v1** (see §3). v1 ships actions only. -5. ✅ Name-based resolution: definitions reference action and trigger types by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. -6. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) - -### Trigger taxonomy -8. ✅ Three trigger types: `schedule`, `webhook`, `event` -9. ✅ Events absorb both connector events and internal SurfSense events -10. ✅ Filter grammar is JSON-structured operators (not Jinja) - -### Templating cluster -11. ✅ Jinja2 `SandboxedEnvironment` for templates and `when:` predicates — but with the explicit understanding that the sandbox is an allowlist-by-default architecture, not a denylist -12. ✅ Zero globals registered. Curated 15 filters only, each audited for safe behavior with hostile input. List grows only by reviewed addition -13. ✅ Four runtime mitigations: `StrictUndefined`, 8 KB template source cap, 100 ms render time cap (watchdog-enforced), 1 MB output size cap -14. ✅ Non-string template values render as JSON by default -15. ✅ Fixed `run.*` namespace, documented -16. ⏸ **Pre-Phase-5 gate**: template sharing across SearchSpaces breaks the approver-equals-runner trust model. Mitigation is a re-approval flow at the import boundary (UX-level), not a template-language migration. Jinja itself stays. - -### Execution -17. ✅ Executor is a Celery task wrapping a sequential loop — not an agent -18. ✅ `when:` is optional per step; false = skipped (not failed) -19. ✅ No DAGs, no parallelism, no loops — composition via agent_task or events -20. ✅ `on_failure` part of execution policy from v1 -21. ✅ Step-level retry and timeout overrides -22. ⏸ Budget cap enforced pre-enqueue and mid-flight — **deferred** until the cost ledger ships (see §8 Budget enforcement) - -### Components -23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable -24. ⏸ Side effects are a set, including `USER_VISIBLE` — **deferred** until multi-user automation RBAC ships -25. ⏸ `expected_duration_seconds` integer drives queue routing — **deferred** until a second Celery queue is needed -26. ⏸ `produces_artifacts` is a list of `ArtifactSpec`, not a bool — **deferred** until artifacts beyond the deliverable handlers' own persistence are needed -27. ✅ Output schemas recommended on `agent_task`; editor warns when missing - -### Event bus -28. ✅ `domain_events` table for v1, with upgrade path to Redis Streams -29. ✅ Automations publish run events for composability -30. ✅ Publish/subscribe behind interface — no direct table access elsewhere - -### Capability unification — all deferred to post-v1 -31. ⏸ One shared catalog of "things this SurfSense instance can do" — **deferred**, see §3 -32. ⏸ Handler `CallContext` (caller user id, search space id, run id) — **deferred** with unification -33. ⏸ Per-capability scope declarations driving authorization — **deferred** with unification -34. ⏸ MCP integration on top of the unification layer (`mcp_connections`, `mcp_tools`, harvester) — **deferred to Phase 4** - -### Credentials — all deferred to Phase 2 -35. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** -36. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** -37. ⏸ Credentials resolved per-call by the handler context, not pre-loaded into worker environment — **Phase 2** -38. ⏸ Tokens encrypted at rest; refresh handled automatically by the handler context — **Phase 2** - -### v1-minimum -39. ✅ v1 ships actions only — no separate capability layer. `ActionDefinition` is five fields: `type`, `name`, `description`, `params_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. -40. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. -41. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. - -### NL authoring -42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary -43. ✅ Generator LLM produces JSON; deterministic schema + resource validation runs before user sees the proposal -44. ✅ Review LLM produces plain-language summary + flagged anomalies for the user — UX layer, not a security boundary -45. ✅ Generator LLM's input is scoped (user prompt + schema + registry only); arbitrary document content is not fed in -46. ✅ Human approval is required before save — no auto-approval option, ever -47. ✅ Every field editable in the proposal; unresolved questions surface as clarifications -48. ✅ NL drafts are transient storage, not a core table - -### Data model -49. ✅ v1 ships three tables (`automations`, `automation_triggers`, `automation_runs`). `domain_events` lands in Phase 3; `mcp_connections` and `mcp_tools` in Phase 4. -50. ✅ Run rows snapshot the definition (immutable history) -51. ✅ All entities scoped by `search_space_id` for RBAC -52. ✅ Editing an automation bumps `version`; existing runs unaffected - ---- - -## 14. Open questions deferred to implementation - -None of these block design; they're decisions a developer will make in -context, with the principle from §1 as their guide. - -- Exact retry backoff formulas (multipliers, jitter, ceilings) -- Webhook signature verification standards (HMAC scheme, header naming) -- Whether to support inline JSON Schema `$ref` to external schemas, or - inline everything -- Specific CDN/storage backend choices for artifacts (probably - whatever SurfSense already uses for podcasts) -- Rate limits per SearchSpace and per user -- Audit log retention policy - ---- - -## 15. Why this is ready to build - -This document satisfies five tests: - -1. **The four worked examples** (digest, CI webhook, file-added-trigger, - weekly podcast) all express cleanly in the contract without special - cases. Each one was used to find gaps before the gaps reached code. - -2. **The audit pass identified six refinements**, all incorporated. No - pending audit items. - -3. **Every decision points back to the principle from §1.** When a future - feature request lands, "does it belong in the definition or in the - engine?" gives a clear answer. - -4. **The build is staged** so Phase 1 ships in weeks, not months, and - each subsequent phase delivers user value while de-risking the next. - -5. **Existing SurfSense infrastructure is reused**, not paralleled. Celery - Beat, PostgreSQL/JSONB, Electric SQL, SQLAlchemy/Alembic, the existing - `tools/registry.py` pattern, the existing Search Space scoping — all - continue to do what they already do. The automation engine is a new - directory, not a new system. - -The next document a developer needs is the Pydantic models and JSON -Schemas spelled out concretely. Those follow mechanically from this plan. - ---- - -*Sources consulted: Claude Code Routines documentation; NousResearch/hermes- -agent (cron and skills subsystems); n8n documentation on node types and -workflow data model; the SurfSense repository and DeepWiki architecture -notes (FastAPI + Celery Beat + Electric SQL + LangGraph Deep Agents + -Search Space RBAC); Model Context Protocol specification for external -tool harvesting; AWS EventBridge for filter grammar; workflow-pattern -literature (van der Aalst et al.) for the trigger / action / concurrency -vocabulary.* From 2d8d42bd9cee790c8884a75cbc1ea5f9aa113126 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 15:40:18 +0200 Subject: [PATCH 078/133] refactor(web): polish automations detail view --- .../automation-detail-content.tsx | 24 ++++---- .../automation-definition-section.tsx | 2 +- .../components/automation-detail-loading.tsx | 60 ++++++++++++------- .../components/automation-runs-section.tsx | 2 +- .../automation-triggers-section.tsx | 2 +- .../components/plan-step-card.tsx | 2 +- .../components/run-details-panel.tsx | 2 +- .../[automation_id]/components/run-row.tsx | 2 +- .../components/runs-loading.tsx | 2 +- .../components/trigger-card.tsx | 2 +- .../new/components/automation-json-form.tsx | 2 +- .../layout/providers/LayoutDataProvider.tsx | 8 ++- 12 files changed, 65 insertions(+), 45 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx index 49df3633e..4085d47a8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -72,16 +72,20 @@ export function AutomationDetailContent({ canDelete={perms.canDelete} /> - <AutomationDefinitionSection definition={automation.definition} /> - - <AutomationTriggersSection - triggers={automation.triggers} - automationId={automation.id} - canUpdate={perms.canUpdate} - canDelete={perms.canDelete} - /> - - <AutomationRunsSection automationId={automation.id} /> + <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> + <div className="space-y-6 min-w-0 lg:col-span-2"> + <AutomationDefinitionSection definition={automation.definition} /> + <AutomationRunsSection automationId={automation.id} /> + </div> + <div className="space-y-6 min-w-0"> + <AutomationTriggersSection + triggers={automation.triggers} + automationId={automation.id} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + /> + </div> + </div> </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx index 9545f363b..e8721d9b0 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx @@ -24,7 +24,7 @@ export function AutomationDefinitionSection({ definition }: AutomationDefinition const hasInputs = !!definition.inputs; return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <CardTitle className="text-base font-semibold">Definition</CardTitle> <span className="text-xs font-mono text-muted-foreground border border-border/60 rounded px-1.5 py-0.5"> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx index 1d01305ee..0d6ba3110 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx @@ -3,12 +3,13 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; /** - * Skeleton for the detail page. Same shell as the loaded view (header + - * two stacked cards) so the layout doesn't jump on data arrival. + * Skeleton for the detail page. Mirrors the loaded view's main/sidebar + * grid (Definition + Runs on the left, Triggers on the right) so layout + * doesn't reflow when data arrives. */ export function AutomationDetailLoading() { return ( - <div className="space-y-6"> + <> <div className="space-y-3"> <Skeleton className="h-4 w-32" /> <div className="flex items-center gap-3"> @@ -18,25 +19,38 @@ export function AutomationDetailLoading() { <Skeleton className="h-4 w-96" /> </div> - <Card> - <CardHeader> - <Skeleton className="h-5 w-32" /> - </CardHeader> - <CardContent className="space-y-4"> - <Skeleton className="h-4 w-3/4" /> - <Skeleton className="h-4 w-1/2" /> - <Skeleton className="h-24 w-full" /> - </CardContent> - </Card> - - <Card> - <CardHeader> - <Skeleton className="h-5 w-24" /> - </CardHeader> - <CardContent> - <Skeleton className="h-20 w-full" /> - </CardContent> - </Card> - </div> + <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> + <div className="space-y-6 min-w-0 lg:col-span-2"> + <Card className="border-border/60 bg-accent"> + <CardHeader> + <Skeleton className="h-5 w-32" /> + </CardHeader> + <CardContent className="space-y-4"> + <Skeleton className="h-4 w-3/4" /> + <Skeleton className="h-4 w-1/2" /> + <Skeleton className="h-24 w-full" /> + </CardContent> + </Card> + <Card className="border-border/60 bg-accent"> + <CardHeader> + <Skeleton className="h-5 w-32" /> + </CardHeader> + <CardContent> + <Skeleton className="h-20 w-full" /> + </CardContent> + </Card> + </div> + <div className="space-y-6 min-w-0"> + <Card className="border-border/60 bg-accent"> + <CardHeader> + <Skeleton className="h-5 w-24" /> + </CardHeader> + <CardContent> + <Skeleton className="h-20 w-full" /> + </CardContent> + </Card> + </div> + </div> + </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx index b6158cab2..d31bd696d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx @@ -23,7 +23,7 @@ export function AutomationRunsSection({ automationId }: AutomationRunsSectionPro const runs = data?.items ?? []; return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <div className="space-y-1"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx index 33c8373a1..558a089ac 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx @@ -23,7 +23,7 @@ export function AutomationTriggersSection({ canDelete, }: AutomationTriggersSectionProps) { return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="pb-4"> <CardTitle className="text-base font-semibold">Triggers</CardTitle> <p className="text-xs text-muted-foreground"> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx index 3feb77712..b9fda00db 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx @@ -15,7 +15,7 @@ interface PlanStepCardProps { */ export function PlanStepCard({ step, index }: PlanStepCardProps) { return ( - <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="rounded-md border border-border/60 overflow-hidden"> <div className="flex items-center gap-2 px-4 py-2 border-b border-border/60 bg-muted/30"> <span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"> {index + 1} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx index d1d46900a..94a96b199 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx @@ -109,7 +109,7 @@ function Section({ function JsonBlock({ value }: { value: unknown }) { return ( - <pre className="rounded-md bg-background/60 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> + <pre className="rounded-md bg-muted/40 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> {JSON.stringify(value, null, 2)} </pre> ); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx index b8d2bcc8b..02ca0569c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx @@ -24,7 +24,7 @@ export function RunRow({ run, automationId }: RunRowProps) { : formatRelativeDate(run.created_at); return ( - <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="rounded-md border border-border/60 overflow-hidden"> <button type="button" onClick={() => setOpen((value) => !value)} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx index 5cab18f4c..61ce25e32 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx @@ -9,7 +9,7 @@ export function RunsLoading() { {ROW_KEYS.map((key) => ( <div key={key} - className="flex items-center justify-between gap-4 rounded-md border border-border/60 bg-background/50 px-4 py-3" + className="flex items-center justify-between gap-4 rounded-md border border-border/60 px-4 py-3" > <div className="flex items-center gap-3"> <Skeleton className="h-5 w-20 rounded-md" /> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index ec0246e49..200a15f57 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -48,7 +48,7 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri return ( <> - <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="rounded-md border border-border/60 overflow-hidden"> <div className="flex items-center justify-between gap-4 px-4 py-3 border-b border-border/60"> <div className="flex items-center gap-3 min-w-0"> <CalendarClock className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx index 845d95166..8fe065295 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx @@ -75,7 +75,7 @@ export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { const hasIssues = issues.length > 0; return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 67971e435..663e4b96f 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -726,9 +726,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid : undefined } workspacePanelContentClassName={ - isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage - ? "max-w-5xl" - : undefined + isAutomationsPage + ? "max-w-none" + : isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage + ? "max-w-5xl" + : undefined } isLoadingChats={isLoadingThreads} activeSlideoutPanel={activeSlideoutPanel} From fa0cdb9760f0b08bfb5db4596c79ffbefdbc845b Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 16:07:54 +0200 Subject: [PATCH 079/133] feat(web): unified json viewer/editor + edit existing automation --- .../automation-definition-section.tsx | 5 +- .../components/automation-detail-header.tsx | 10 +- .../components/inputs-schema-preview.tsx | 15 +- .../components/plan-step-card.tsx | 7 +- .../components/run-details-panel.tsx | 7 +- .../components/trigger-card.tsx | 7 +- .../edit/automation-edit-content.tsx | 56 ++++++ .../edit/components/automation-edit-form.tsx | 121 +++++++++++++ .../automations/[automation_id]/edit/page.tsx | 18 ++ .../new/components/automation-json-form.tsx | 62 +++---- .../components/json-metadata-viewer.tsx | 11 +- surfsense_web/components/json-view.tsx | 93 ++++++++++ .../tool-ui/automation/create-automation.tsx | 50 ++---- surfsense_web/package.json | 2 +- surfsense_web/pnpm-lock.yaml | 159 ++++++++++++++++-- 15 files changed, 504 insertions(+), 119 deletions(-) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx create mode 100644 surfsense_web/components/json-view.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx index e8721d9b0..4ff9b8b8c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx @@ -11,9 +11,8 @@ interface AutomationDefinitionSectionProps { } /** - * The Definition card. Read-only in v1 — editing definitions happens via - * chat (re-run create_automation with a refined intent) or, later, via - * the raw-JSON path. Layout is top-down: + * The Definition card. Read view; editing happens on the sibling /edit + * route (Edit button in the header). Layout is top-down: * goal → tags → execution defaults → inputs schema (if any) → plan * * The schema_version is rendered as a small badge next to the section diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx index 4cf3efcc1..0bce3fa2d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx @@ -1,6 +1,6 @@ "use client"; import { useAtomValue } from "jotai"; -import { ArrowLeft, Pause, Play, Trash2 } from "lucide-react"; +import { ArrowLeft, Pause, Pencil, Play, Trash2 } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useCallback, useState } from "react"; @@ -82,6 +82,14 @@ export function AutomationDetailHeader({ </div> <div className="flex items-center gap-2 shrink-0"> + {canUpdate && ( + <Button asChild type="button" variant="outline" size="sm"> + <Link href={`/dashboard/${searchSpaceId}/automations/${automation.id}/edit`}> + <Pencil className="mr-2 h-4 w-4" /> + Edit + </Link> + </Button> + )} {canToggle && ( <Button type="button" diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx index bf2db8986..29d79d99b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx @@ -1,4 +1,5 @@ "use client"; +import { JsonView } from "@/components/json-view"; import type { Inputs } from "@/contracts/types/automation.types"; interface InputsSchemaPreviewProps { @@ -6,15 +7,15 @@ interface InputsSchemaPreviewProps { } /** - * Read-only JSON preview of an automation's accepted-inputs schema. - * Most automations don't define inputs (defaults are baked into the - * trigger's static_inputs), so the parent skips rendering this card - * when ``inputs`` is null. + * Read-only preview of an automation's accepted-inputs schema. Most + * automations don't define inputs (defaults are baked into the trigger's + * static_inputs), so the parent skips rendering this card when ``inputs`` + * is null. */ export function InputsSchemaPreview({ inputs }: InputsSchemaPreviewProps) { return ( - <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-72"> - {JSON.stringify(inputs.schema, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 max-h-72 overflow-auto"> + <JsonView src={inputs.schema} collapsed={2} /> + </div> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx index b9fda00db..27cecf3bf 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx @@ -1,5 +1,6 @@ "use client"; import { ArrowRightCircle, GitCommitHorizontal } from "lucide-react"; +import { JsonView } from "@/components/json-view"; import type { PlanStep } from "@/contracts/types/automation.types"; interface PlanStepCardProps { @@ -54,9 +55,9 @@ export function PlanStepCard({ step, index }: PlanStepCardProps) { <GitCommitHorizontal className="h-3.5 w-3.5" aria-hidden /> Params </div> - <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> - {JSON.stringify(step.params, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> + <JsonView src={step.params} collapsed={1} /> + </div> </div> </div> </div> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx index 94a96b199..f9c6fbb5a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx @@ -1,5 +1,6 @@ "use client"; import { AlertCircle, FileOutput, GitCommitHorizontal, Package, Settings2 } from "lucide-react"; +import { JsonView } from "@/components/json-view"; import { Skeleton } from "@/components/ui/skeleton"; import { useAutomationRun } from "@/hooks/use-automation-runs"; @@ -109,8 +110,8 @@ function Section({ function JsonBlock({ value }: { value: unknown }) { return ( - <pre className="rounded-md bg-muted/40 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> - {JSON.stringify(value, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 max-h-64 overflow-auto"> + <JsonView src={value} collapsed={1} /> + </div> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index 200a15f57..a1d84d2d7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -3,6 +3,7 @@ import { useAtomValue } from "jotai"; import { CalendarClock, Clock, Trash2 } from "lucide-react"; import { useState } from "react"; import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import type { Trigger } from "@/contracts/types/automation.types"; @@ -109,9 +110,9 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri {hasStaticInputs && ( <div> <div className="text-muted-foreground mb-1">Static inputs</div> - <pre className="rounded-md bg-muted/40 px-3 py-2 font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> - {JSON.stringify(trigger.static_inputs, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> + <JsonView src={trigger.static_inputs} collapsed={1} /> + </div> </div> )} </div> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx new file mode 100644 index 000000000..219552a1a --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx @@ -0,0 +1,56 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomation } from "@/hooks/use-automation"; +import { useAutomationPermissions } from "../../hooks/use-automation-permissions"; +import { AutomationDetailLoading } from "../components/automation-detail-loading"; +import { AutomationNotFound } from "../components/automation-not-found"; +import { AutomationEditForm } from "./components/automation-edit-form"; + +interface AutomationEditContentProps { + searchSpaceId: number; + automationId: number; +} + +/** + * Client orchestrator for the edit route. Mirrors detail-content's branch + * structure but gates on ``canUpdate`` instead of ``canRead``: a user who + * can read but not update is bounced to the access-denied panel. + */ +export function AutomationEditContent({ + searchSpaceId, + automationId, +}: AutomationEditContentProps) { + const perms = useAutomationPermissions(); + const validId = Number.isInteger(automationId) && automationId > 0; + const { data: automation, isLoading, error } = useAutomation(validId ? automationId : undefined); + + if (perms.loading) { + return <AutomationDetailLoading />; + } + + if (!perms.canUpdate) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to edit automations in this search space. + </p> + </div> + ); + } + + if (!validId) { + return <AutomationNotFound searchSpaceId={searchSpaceId} />; + } + + if (isLoading) { + return <AutomationDetailLoading />; + } + + if (error || !automation) { + return <AutomationNotFound searchSpaceId={searchSpaceId} error={error} />; + } + + return <AutomationEditForm automation={automation} searchSpaceId={searchSpaceId} />; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx new file mode 100644 index 000000000..86b355838 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx @@ -0,0 +1,121 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { AlertCircle, ArrowLeft, Save } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { JsonView } from "@/components/json-view"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { + type Automation, + automationUpdateRequest, +} from "@/contracts/types/automation.types"; + +interface AutomationEditFormProps { + automation: Automation; + searchSpaceId: number; +} + +/** + * Edit-existing-automation form. Surfaces the four mutable fields + * (name, description, status, definition) as one editable JSON tree; + * triggers stay on the detail page where they have their own management + * UI. Validates with the same Zod schema the API expects, then PATCHes + * the changed shape back. + */ +export function AutomationEditForm({ automation, searchSpaceId }: AutomationEditFormProps) { + const router = useRouter(); + const { mutateAsync: updateAutomation, isPending } = useAtomValue(updateAutomationMutationAtom); + const detailHref = `/dashboard/${searchSpaceId}/automations/${automation.id}`; + + const [value, setValue] = useState(() => ({ + name: automation.name, + description: automation.description ?? null, + status: automation.status, + definition: automation.definition, + })); + const [issues, setIssues] = useState<string[]>([]); + + async function handleSave() { + setIssues([]); + const result = automationUpdateRequest.safeParse(value); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + try { + await updateAutomation({ automationId: automation.id, patch: result.data }); + router.push(detailHref); + } catch (err) { + setIssues([(err as Error).message ?? "Update failed"]); + } + } + + return ( + <> + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link href={detailHref} className="text-xs text-muted-foreground"> + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automation + </Link> + </Button> + <div> + <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> + Edit automation + </h1> + <p className="text-sm text-muted-foreground mt-1">{automation.name}</p> + </div> + </div> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-4"> + <CardTitle className="text-base font-semibold">Definition</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[36rem] overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => setValue(next as typeof value)} + collapsed={false} + /> + </div> + + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + + <div className="flex items-center justify-end gap-2"> + <Button asChild type="button" variant="ghost" size="sm"> + <Link href={detailHref}>Cancel</Link> + </Button> + <Button type="button" onClick={handleSave} disabled={isPending} size="sm"> + {isPending ? ( + <Spinner size="xs" className="mr-2" /> + ) : ( + <Save className="mr-2 h-4 w-4" /> + )} + Save changes + </Button> + </div> + </CardContent> + </Card> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx new file mode 100644 index 000000000..8477b9e12 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx @@ -0,0 +1,18 @@ +import { AutomationEditContent } from "./automation-edit-content"; + +export default async function AutomationEditPage({ + params, +}: { + params: Promise<{ search_space_id: string; automation_id: string }>; +}) { + const { search_space_id, automation_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationEditContent + searchSpaceId={Number(search_space_id)} + automationId={Number(automation_id)} + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx index 8fe065295..94b608b8f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx @@ -1,9 +1,10 @@ "use client"; import { useAtomValue } from "jotai"; -import { AlertCircle, Code, FileJson, Save } from "lucide-react"; +import { AlertCircle, FileJson, Save } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { createAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; @@ -17,45 +18,24 @@ interface AutomationJsonFormProps { /** * Raw-JSON create form. Lets power users skip the chat drafter when they * already know the shape they want. Flow: - * parse JSON → inject search_space_id → Zod validate → POST → navigate + * edit tree → inject search_space_id → Zod validate → POST → navigate * - * ``search_space_id`` is injected here rather than required in the pasted - * payload — the user shouldn't have to know their numeric id, and it - * keeps the template copy-paste-friendly across search spaces. + * ``search_space_id`` is injected here rather than required in the edited + * tree — the user shouldn't have to know their numeric id, and it keeps + * the template copy-paste-friendly across search spaces. */ export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { const router = useRouter(); const { mutateAsync: createAutomation, isPending } = useAtomValue(createAutomationMutationAtom); - const [text, setText] = useState(() => JSON.stringify(DEFAULT_AUTOMATION_TEMPLATE, null, 2)); + const [value, setValue] = useState<Record<string, unknown>>( + () => DEFAULT_AUTOMATION_TEMPLATE as Record<string, unknown> + ); const [issues, setIssues] = useState<string[]>([]); - function handleFormat() { - try { - const parsed = JSON.parse(text); - setText(JSON.stringify(parsed, null, 2)); - setIssues([]); - } catch (err) { - setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); - } - } - async function handleSubmit() { setIssues([]); - let parsed: unknown; - try { - parsed = JSON.parse(text); - } catch (err) { - setIssues([`Invalid JSON: ${(err as Error).message}`]); - return; - } - - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { - setIssues(["Root must be a JSON object."]); - return; - } - - const payload = { ...(parsed as Record<string, unknown>), search_space_id: searchSpaceId }; + const payload = { ...value, search_space_id: searchSpaceId }; const result = automationCreateRequest.safeParse(payload); if (!result.success) { setIssues( @@ -76,25 +56,21 @@ export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { return ( <Card className="border-border/60 bg-accent"> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <CardHeader className="pb-4"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> Definition + triggers </CardTitle> - <Button type="button" variant="outline" size="sm" onClick={handleFormat}> - <Code className="mr-2 h-3.5 w-3.5" /> - Format - </Button> </CardHeader> <CardContent className="space-y-4"> - <textarea - value={text} - onChange={(e) => setText(e.target.value)} - spellCheck={false} - rows={24} - className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[16rem]" - aria-label="Automation JSON" - /> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[32rem] overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => setValue(next as Record<string, unknown>)} + collapsed={false} + /> + </div> {hasIssues && ( <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> diff --git a/surfsense_web/components/json-metadata-viewer.tsx b/surfsense_web/components/json-metadata-viewer.tsx index cc87a75c5..8ebbbc84e 100644 --- a/surfsense_web/components/json-metadata-viewer.tsx +++ b/surfsense_web/components/json-metadata-viewer.tsx @@ -1,6 +1,6 @@ import { FileJson } from "lucide-react"; import React from "react"; -import { defaultStyles, JsonView } from "react-json-view-lite"; +import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -10,7 +10,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Spinner } from "@/components/ui/spinner"; -import "react-json-view-lite/dist/index.css"; interface JsonMetadataViewerProps { title: string; @@ -56,13 +55,13 @@ export function JsonMetadataViewer({ {title} - Metadata </DialogTitle> </DialogHeader> - <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm"> + <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm overflow-auto"> {loading ? ( <div className="flex items-center justify-center py-12"> <Spinner size="lg" className="text-muted-foreground" /> </div> ) : ( - <JsonView data={jsonData} style={defaultStyles} /> + <JsonView src={jsonData} collapsed={2} /> )} </div> </DialogContent> @@ -87,8 +86,8 @@ export function JsonMetadataViewer({ {title} - Metadata </DialogTitle> </DialogHeader> - <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm"> - <JsonView data={jsonData} style={defaultStyles} /> + <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm overflow-auto"> + <JsonView src={jsonData} collapsed={2} /> </div> </DialogContent> </Dialog> diff --git a/surfsense_web/components/json-view.tsx b/surfsense_web/components/json-view.tsx new file mode 100644 index 000000000..c293828b3 --- /dev/null +++ b/surfsense_web/components/json-view.tsx @@ -0,0 +1,93 @@ +"use client"; + +import ReactJson, { type InteractionProps } from "@microlink/react-json-view"; +import { useTheme } from "next-themes"; +import { useCallback, useMemo } from "react"; + +/** + * Shared JSON viewer/editor wrapper around @microlink/react-json-view. + * + * One component, dual mode: passing ``editable`` + ``onChange`` enables + * inline value editing, key renaming, add and delete. Omitting them + * yields a read-only viewer. The underlying library is uncontrolled — it + * mutates its own internal copy of ``src`` and surfaces the final tree on + * each interaction via ``updated_src``, which we forward to ``onChange``. + * + * Theme follows ``next-themes``: a dark base-16 palette in dark mode, the + * library's neutral default in light mode. Defaults are tuned for our + * compact UI surfaces (no data-type labels, no key quotes, triangle icons, + * tight indent). + */ +export interface JsonViewProps { + /** The JSON value to display. Primitives are wrapped under ``{ value }`` + * because the underlying library requires an object root. */ + src: unknown; + /** Enables value/key editing + add + delete. Requires ``onChange`` to + * observe the result; without it the toggle is silently a no-op. */ + editable?: boolean; + /** Called with the full updated tree on every accepted interaction. */ + onChange?: (next: unknown) => void; + /** Collapse depth. ``true`` collapses everything past the root; a number + * collapses from that depth onward. */ + collapsed?: boolean | number; + /** Root label. Default ``false`` (no label — saves vertical space). */ + name?: string | false; + className?: string; +} + +const DARK_THEME = "monokai" as const; +const LIGHT_THEME = "rjv-default" as const; + +const SHARED_DEFAULTS = { + iconStyle: "triangle" as const, + indentWidth: 2, + enableClipboard: true, + displayDataTypes: false, + displayObjectSize: true, + quotesOnKeys: false, + collapseStringsAfterLength: 80, +}; + +export function JsonView({ + src, + editable = false, + onChange, + collapsed = 2, + name = false, + className, +}: JsonViewProps) { + const { resolvedTheme } = useTheme(); + const theme = resolvedTheme === "dark" ? DARK_THEME : LIGHT_THEME; + + // The library throws on non-object roots. Wrap primitives and null/undefined. + const safeSrc = useMemo(() => { + if (src && typeof src === "object") return src as object; + return { value: src }; + }, [src]); + + const handleChange = useCallback( + (interaction: InteractionProps) => { + onChange?.(interaction.updated_src); + return true; + }, + [onChange] + ); + + const interactive = editable && onChange ? handleChange : (false as const); + + return ( + <div className={className}> + <ReactJson + src={safeSrc} + name={name} + theme={theme} + collapsed={collapsed} + onEdit={interactive} + onAdd={interactive} + onDelete={interactive} + style={{ backgroundColor: "transparent", fontSize: 12, fontFamily: "var(--font-mono)" }} + {...SHARED_DEFAULTS} + /> + </div> + ); +} diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index 00b120d38..b152f9055 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -2,17 +2,11 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; -import { - AlertCircle, - Code, - CornerDownLeftIcon, - ExternalLink, - Pencil, - Workflow, -} from "lucide-react"; +import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react"; import Link from "next/link"; import { useCallback, useEffect, useMemo, useState } from "react"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { JsonView } from "@/components/json-view"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { automationCreateRequest } from "@/contracts/types/automation.types"; @@ -231,28 +225,12 @@ interface JsonEditorProps { } function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { - const [text, setText] = useState(() => JSON.stringify(initialValue, null, 2)); + const [value, setValue] = useState<Record<string, unknown>>(initialValue); const [issues, setIssues] = useState<string[]>([]); - function handleFormat() { - try { - setText(JSON.stringify(JSON.parse(text), null, 2)); - setIssues([]); - } catch (err) { - setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); - } - } - function handleSave() { setIssues([]); - let parsed: unknown; - try { - parsed = JSON.parse(text); - } catch (err) { - setIssues([`Invalid JSON: ${(err as Error).message}`]); - return; - } - const result = editArgsSchema.safeParse(parsed); + const result = editArgsSchema.safeParse(value); if (!result.success) { setIssues( result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) @@ -264,14 +242,14 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { return ( <div className="space-y-3"> - <textarea - value={text} - onChange={(e) => setText(e.target.value)} - spellCheck={false} - rows={16} - className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[12rem]" - aria-label="Automation JSON" - /> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[24rem] overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => setValue(next as Record<string, unknown>)} + collapsed={false} + /> + </div> {issues.length > 0 && ( <div className="rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2"> <div className="flex items-center gap-1.5 text-xs font-medium text-destructive"> @@ -291,10 +269,6 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { <Button type="button" variant="ghost" size="sm" onClick={onCancel}> Cancel </Button> - <Button type="button" variant="outline" size="sm" onClick={handleFormat}> - <Code className="mr-1.5 h-3.5 w-3.5" /> - Format - </Button> <Button type="button" size="sm" onClick={handleSave}> Save edits </Button> diff --git a/surfsense_web/package.json b/surfsense_web/package.json index 213adbaad..6ac32160b 100644 --- a/surfsense_web/package.json +++ b/surfsense_web/package.json @@ -36,6 +36,7 @@ "@babel/standalone": "^7.29.2", "@hookform/resolvers": "^5.2.2", "@marsidev/react-turnstile": "^1.5.0", + "@microlink/react-json-view": "^1.31.20", "@monaco-editor/react": "^4.7.0", "@number-flow/react": "^0.5.10", "@platejs/autoformat": "^52.0.11", @@ -134,7 +135,6 @@ "react-dom": "^19.2.3", "react-dropzone": "^14.3.8", "react-hook-form": "^7.61.1", - "react-json-view-lite": "^2.4.1", "react-syntax-highlighter": "^15.6.1", "react-wrap-balancer": "^1.1.1", "rehype-katex": "^7.0.1", diff --git a/surfsense_web/pnpm-lock.yaml b/surfsense_web/pnpm-lock.yaml index 8602feb8d..7cbff6923 100644 --- a/surfsense_web/pnpm-lock.yaml +++ b/surfsense_web/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@marsidev/react-turnstile': specifier: ^1.5.0 version: 1.5.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@microlink/react-json-view': + specifier: ^1.31.20 + version: 1.31.20(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@monaco-editor/react': specifier: ^4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -323,9 +326,6 @@ importers: react-hook-form: specifier: ^7.61.1 version: 7.71.2(react@19.2.4) - react-json-view-lite: - specifier: ^2.4.1 - version: 2.5.0(react@19.2.4) react-syntax-highlighter: specifier: ^15.6.1 version: 15.6.6(react@19.2.4) @@ -1143,24 +1143,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.4.6': resolution: {integrity: sha512-kMLaI7OF5GN1Q8Doymjro1P8rVEoy7BKQALNz6fiR8IC1WKduoNyteBtJlHT7ASIL0Cx2jR6VUOBIbcB1B8pew==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.6': resolution: {integrity: sha512-C9s98IPDu7DYarjlZNuzJKTjVHN03RUnmHV5htvqsx6vEUXCDSJ59DNwjKVD5XYoSS4N+BYhq3RTBAL8X6svEg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.4.6': resolution: {integrity: sha512-oHXmUFEoH8Lql1xfc3QkFLiC1hGR7qedv5eKNlC185or+o4/4HiaU7vYODAH3peRCfsuLr1g6v2fK9dFFOYdyw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.4.6': resolution: {integrity: sha512-xzThn87Pf3YrOGTEODFGONmqXpTwUNxovQb72iaUOdcw8sBSY3+3WD8Hm9IhMYLnPi0n32s3L3NWU6+eSjfqFg==} @@ -1836,89 +1840,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1989,6 +2009,13 @@ packages: peerDependencies: mediabunny: ^1.0.0 + '@microlink/react-json-view@1.31.20': + resolution: {integrity: sha512-gNLkGvjFDeAqVGvK3H7lfoDqetn/9lW2ugiYiJhchc7jQU1ZaKsZnt97ANluXWFfd/wifoA9TrVOTsUXwXCJwA==} + engines: {node: '>=17'} + peerDependencies: + react: '>= 15' + react-dom: '>= 15' + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -2028,30 +2055,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-arm64-musl@0.1.97': resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-x64-gnu@0.1.97': resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-x64-musl@0.1.97': resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/canvas-win32-arm64-msvc@0.1.97': resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==} @@ -2095,24 +2127,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -2768,48 +2804,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.45.0': resolution: {integrity: sha512-XQKXZIKYJC3GQJ8FnD3iMntpw69Wd9kDDK/Xt79p6xnFYlGGxSNv2vIBvRTDg5CKByWFWWZLCRDOXoP/m6YN4g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.45.0': resolution: {integrity: sha512-+g5RiG+xOkdrCWkKodv407nTvMq4vYM18Uox2MhZBm/YoqFxxJpWKsloskFFG5NU13HGPw1wzYjjOVcyd9moCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.45.0': resolution: {integrity: sha512-V7dXKoSyEbWAkkSF4JJNtF+NJZDmJoSarSoP30WCsB3X636Rehd3CvxBj49FIJxEBFWhvcUjGSHVeU8Erck1bQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.45.0': resolution: {integrity: sha512-Vdelft1sAEYojVGgcODEFXSWYQYlIvoyIGWebKCuUibd1tvS1TjTx413xG2ZLuHpYj45CkN/ztMLMX6jrgqpgg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.45.0': resolution: {integrity: sha512-RR7xKgNpqwENnK0aYCGYg0JycY2n93J0reNjHyes+I9Gq52dH95x+CBlnlAQHCPfz6FGnKA9HirgUl14WO6o7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.45.0': resolution: {integrity: sha512-U/QQ0+BQNSHxjuXR/utvXnQ50Vu5kUuqEomZvQ1/3mhgbBiMc2WU9q5kZ5WwLp3gnFIx9ibkveoRSe2EZubkqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.45.0': resolution: {integrity: sha512-o5TLOUCF0RWQjsIS06yVC+kFgp092/yLe6qBGSUvtnmTVw9gxjpdQSXc3VN5Cnive4K11HNstEZF8ROKHfDFSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxfmt/binding-openharmony-arm64@0.45.0': resolution: {integrity: sha512-RnGcV3HgPuOjsGx/k9oyRNKmOp+NBLGzZTdPDYbc19r7NGeYPplnUU/BfU35bX2Y/O4ejvHxcfkvW2WoYL/gsg==} @@ -2864,36 +2908,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -4222,66 +4272,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -4472,24 +4535,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.13': resolution: {integrity: sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.13': resolution: {integrity: sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.13': resolution: {integrity: sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.13': resolution: {integrity: sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg==} @@ -4573,24 +4640,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -4732,6 +4803,9 @@ packages: '@types/katex@0.16.8': resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -4915,41 +4989,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -5323,6 +5405,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} @@ -6471,6 +6560,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -6838,24 +6930,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -6880,6 +6976,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -7673,6 +7772,9 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-base16-styling@0.10.0: + resolution: {integrity: sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==} + react-compiler-runtime@1.0.0: resolution: {integrity: sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==} peerDependencies: @@ -7729,11 +7831,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-json-view-lite@2.5.0: - resolution: {integrity: sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==} - engines: {node: '>=18'} - peerDependencies: - react: ^18.0.0 || ^19.0.0 + react-lifecycles-compat@3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} @@ -8121,6 +8220,9 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + slate-dom@0.119.0: resolution: {integrity: sha512-foc8a2NkE+1SldDIYaoqjhVKupt8RSuvHI868rfYOcypD4we5TT7qunjRKJ852EIRh/Ql8sSTepXgXKOUJnt1w==} peerDependencies: @@ -10316,6 +10418,16 @@ snapshots: dependencies: mediabunny: 1.39.2 + '@microlink/react-json-view@1.31.20(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + react: 19.2.4 + react-base16-styling: 0.10.0 + react-dom: 19.2.4(react@19.2.4) + react-lifecycles-compat: 3.0.4 + react-textarea-autosize: 8.5.9(@types/react@19.2.14)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 @@ -13283,6 +13395,8 @@ snapshots: '@types/katex@0.16.8': {} + '@types/lodash@4.17.24': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -13905,6 +14019,16 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + comma-separated-tokens@1.0.8: {} comma-separated-tokens@2.0.3: {} @@ -15327,6 +15451,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.4: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -15662,6 +15788,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + lodash.camelcase@4.3.0: {} lodash.debounce@4.0.8: {} @@ -16843,6 +16971,13 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-base16-styling@0.10.0: + dependencies: + '@types/lodash': 4.17.24 + color: 4.2.3 + csstype: 3.2.3 + lodash-es: 4.18.1 + react-compiler-runtime@1.0.0(react@19.2.4): dependencies: react: 19.2.4 @@ -16894,9 +17029,7 @@ snapshots: react-is@16.13.1: {} - react-json-view-lite@2.5.0(react@19.2.4): - dependencies: - react: 19.2.4 + react-lifecycles-compat@3.0.4: {} react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): dependencies: @@ -17458,6 +17591,10 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + slate-dom@0.119.0(slate@0.120.0): dependencies: '@juggle/resize-observer': 3.4.0 From 4f202e1fa380c13e0dc8e81a3044335306c1ce7b Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 17:58:00 +0200 Subject: [PATCH 080/133] feat(web): inline edit on trigger cards --- .../components/trigger-card.tsx | 164 +++++++++++++++--- 1 file changed, 137 insertions(+), 27 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index a1d84d2d7..681877523 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -1,12 +1,13 @@ "use client"; import { useAtomValue } from "jotai"; -import { CalendarClock, Clock, Trash2 } from "lucide-react"; +import { AlertCircle, CalendarClock, Clock, Pencil, Save, Trash2 } from "lucide-react"; import { useState } from "react"; import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; -import type { Trigger } from "@/contracts/types/automation.types"; +import { type Trigger, triggerUpdateRequest } from "@/contracts/types/automation.types"; import { describeCron } from "@/lib/automations/describe-cron"; import { formatRelativeDate, formatRelativeFutureDate } from "@/lib/format-date"; import { DeleteTriggerDialog } from "./delete-trigger-dialog"; @@ -18,20 +19,36 @@ interface TriggerCardProps { canDelete: boolean; } +interface TriggerDraft { + params: Record<string, unknown>; + static_inputs: Record<string, unknown>; +} + +function draftFromTrigger(trigger: Trigger): TriggerDraft { + return { + params: trigger.params, + static_inputs: trigger.static_inputs ?? {}, + }; +} + /** * One trigger row in the Triggers section of the detail page. Renders: * - type icon + human-readable schedule + timezone * - last_fired_at / next_fire_at hints * - static_inputs as formatted JSON (when present) - * - enable toggle + remove button (each gated independently) + * - enable toggle + remove button + inline edit (each gated independently) * - * Editing params (cron, timezone, static_inputs) lives behind the future - * raw-JSON path; this card stays read-only-except-for-toggle for v1. + * Inline edit covers ``params`` and ``static_inputs`` — the two fields the + * backend ``PATCH /triggers/[id]`` endpoint accepts beyond ``enabled``. + * ``enabled`` stays on the Switch so the two surfaces don't fight. */ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: TriggerCardProps) { const { mutateAsync: updateTrigger, isPending: updating } = useAtomValue(updateTriggerMutationAtom); const [deleteOpen, setDeleteOpen] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [draft, setDraft] = useState<TriggerDraft>(() => draftFromTrigger(trigger)); + const [issues, setIssues] = useState<string[]>([]); const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; @@ -47,6 +64,38 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri }); } + function startEdit() { + setDraft(draftFromTrigger(trigger)); + setIssues([]); + setIsEditing(true); + } + + function cancelEdit() { + setIsEditing(false); + setIssues([]); + } + + async function saveEdit() { + setIssues([]); + const result = triggerUpdateRequest.safeParse(draft); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + try { + await updateTrigger({ + automationId, + triggerId: trigger.id, + patch: result.data, + }); + setIsEditing(false); + } catch (err) { + setIssues([(err as Error).message ?? "Update failed"]); + } + } + return ( <> <div className="rounded-md border border-border/60 overflow-hidden"> @@ -71,17 +120,29 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri <Switch checked={trigger.enabled} onCheckedChange={handleToggle} - disabled={updating} + disabled={updating || isEditing} aria-label={trigger.enabled ? "Disable trigger" : "Enable trigger"} /> </div> )} + {canUpdate && !isEditing && ( + <Button + variant="ghost" + size="icon" + className="h-8 w-8 text-muted-foreground" + onClick={startEdit} + aria-label="Edit trigger" + > + <Pencil className="h-4 w-4" /> + </Button> + )} {canDelete && ( <Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-destructive" onClick={() => setDeleteOpen(true)} + disabled={isEditing} aria-label="Remove trigger" > <Trash2 className="h-4 w-4" /> @@ -91,29 +152,78 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri </div> <div className="px-4 py-3 space-y-3 text-xs"> - {(trigger.last_fired_at || trigger.next_fire_at) && ( - <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1"> - {trigger.next_fire_at && ( - <TimeRow - label="Next fire" - iso={trigger.next_fire_at} - tense="future" - highlight={trigger.enabled} + {isEditing ? ( + <> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[24rem] overflow-auto"> + <JsonView + src={draft} + editable + onChange={(next) => setDraft(next as TriggerDraft)} + collapsed={false} /> - )} - {trigger.last_fired_at && ( - <TimeRow label="Last fired" iso={trigger.last_fired_at} tense="past" /> - )} - </dl> - )} - - {hasStaticInputs && ( - <div> - <div className="text-muted-foreground mb-1">Static inputs</div> - <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> - <JsonView src={trigger.static_inputs} collapsed={1} /> </div> - </div> + + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 font-medium text-destructive mb-1"> + <AlertCircle className="h-3 w-3" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + + <div className="flex items-center justify-end gap-2"> + <Button + type="button" + variant="ghost" + size="sm" + onClick={cancelEdit} + disabled={updating} + > + Cancel + </Button> + <Button type="button" size="sm" onClick={saveEdit} disabled={updating}> + {updating ? ( + <Spinner size="xs" className="mr-1.5" /> + ) : ( + <Save className="mr-1.5 h-3.5 w-3.5" /> + )} + Save + </Button> + </div> + </> + ) : ( + <> + {(trigger.last_fired_at || trigger.next_fire_at) && ( + <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1"> + {trigger.next_fire_at && ( + <TimeRow + label="Next fire" + iso={trigger.next_fire_at} + tense="future" + highlight={trigger.enabled} + /> + )} + {trigger.last_fired_at && ( + <TimeRow label="Last fired" iso={trigger.last_fired_at} tense="past" /> + )} + </dl> + )} + + {hasStaticInputs && ( + <div> + <div className="text-muted-foreground mb-1">Static inputs</div> + <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> + <JsonView src={trigger.static_inputs} collapsed={1} /> + </div> + </div> + )} + </> )} </div> </div> From 2a76f43387276576be0513496c643f080644b6bd Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:02:52 +0200 Subject: [PATCH 081/133] test(automations/triggers): lock schedule cron + params Cover the cron + IANA timezone + UTC normalization contract for the schedule trigger: next-match strictly-after, DST offset shift across spring-forward, malformed cron / unknown timezone rejection, and the ScheduleTriggerParams Pydantic gate that surfaces InvalidCronError as ValidationError at the API boundary. 8 tests, pure unit (no DB, no mocks). --- .../unit/automations/triggers/__init__.py | 0 .../automations/triggers/schedule/__init__.py | 0 .../triggers/schedule/test_cron.py | 82 +++++++++++++++++++ .../triggers/schedule/test_params.py | 34 ++++++++ 4 files changed, 116 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/triggers/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/triggers/schedule/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py create mode 100644 surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py diff --git a/surfsense_backend/tests/unit/automations/triggers/__init__.py b/surfsense_backend/tests/unit/automations/triggers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/__init__.py b/surfsense_backend/tests/unit/automations/triggers/schedule/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py new file mode 100644 index 000000000..261e51b18 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py @@ -0,0 +1,82 @@ +"""Lock the cron + timezone + UTC normalization contract.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest + +from app.automations.triggers.schedule.cron import ( + InvalidCronError, + compute_next_fire_at, + validate_cron, +) + +pytestmark = pytest.mark.unit + + +def test_compute_next_fire_at_returns_next_match_normalized_to_utc() -> None: + """``compute_next_fire_at`` evaluates the cron in the given IANA timezone + and returns the next strictly-later match expressed in UTC. + + Setup: ``0 9 * * 1-5`` (09:00 Monday-Friday) in ``Africa/Kigali`` + (UTC+2, no DST). With ``after`` = Tuesday 05:00 UTC (= 07:00 local), + the next fire is the same Tuesday at 09:00 local = 07:00 UTC. + """ + after = datetime(2026, 5, 26, 5, 0, tzinfo=UTC) # Tue 07:00 Kigali + + next_fire = compute_next_fire_at("0 9 * * 1-5", "Africa/Kigali", after=after) + + assert next_fire == datetime(2026, 5, 26, 7, 0, tzinfo=UTC) + + +def test_compute_next_fire_at_respects_dst_offset_change() -> None: + """A daily cron in a DST-observing tz fires at the same local hour + across the DST boundary, which produces a different UTC offset on + either side of the transition. + + Setup: ``0 9 * * *`` (09:00 every day) in ``America/New_York``. + NY is UTC-5 in winter (EST), UTC-4 in summer (EDT). Evaluating from + each side of the spring-forward in 2026 (Sun Mar 8 at 02:00 → 03:00): + + - winter: ``after`` = 2026-02-15 (EST, UTC-5) → next 09:00 EST = 14:00 UTC + - summer: ``after`` = 2026-04-15 (EDT, UTC-4) → next 09:00 EDT = 13:00 UTC + """ + winter_after = datetime(2026, 2, 15, 0, 0, tzinfo=UTC) + summer_after = datetime(2026, 4, 15, 0, 0, tzinfo=UTC) + + winter_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=winter_after) + summer_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=summer_after) + + assert winter_fire == datetime(2026, 2, 15, 14, 0, tzinfo=UTC) + assert summer_fire == datetime(2026, 4, 15, 13, 0, tzinfo=UTC) + + +def test_compute_next_fire_at_is_strictly_after_when_after_equals_a_match() -> None: + """When ``after`` lands exactly on a cron match, the result jumps to the + next match — never the same instant. Required so the schedule-tick + can pass ``next_fire_at`` itself as ``after`` to advance to the + following slot without double-firing. + + Setup: weekday 09:00 Kigali. ``after`` = Mon 09:00 Kigali = 07:00 UTC + (an exact match) → next fire must be Tue 09:00 Kigali = next day 07:00 UTC. + """ + after = datetime(2026, 5, 25, 7, 0, tzinfo=UTC) # Mon 09:00 Kigali — exact match + + next_fire = compute_next_fire_at("0 9 * * 1-5", "Africa/Kigali", after=after) + + assert next_fire == datetime(2026, 5, 26, 7, 0, tzinfo=UTC) # Tue 09:00 Kigali + + +def test_validate_cron_rejects_malformed_cron_expression() -> None: + """A syntactically invalid cron must be rejected at validation time so + bad triggers can't reach storage and explode at fire time.""" + with pytest.raises(InvalidCronError): + validate_cron("this is not cron", "UTC") + + +def test_validate_cron_rejects_unknown_timezone() -> None: + """A non-IANA timezone string must be rejected at validation time — + the same protective gate as the cron expression itself.""" + with pytest.raises(InvalidCronError): + validate_cron("0 9 * * *", "Mars/Olympus_Mons") diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py b/surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py new file mode 100644 index 000000000..be98c5be1 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py @@ -0,0 +1,34 @@ +"""Lock the ``ScheduleTriggerParams`` validation contract.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.triggers.schedule.params import ScheduleTriggerParams + +pytestmark = pytest.mark.unit + + +def test_schedule_params_accept_valid_cron_and_iana_timezone() -> None: + """A well-formed cron + IANA timezone yields a populated model. + Locks the round-trip path users go through when creating a trigger.""" + params = ScheduleTriggerParams(cron="0 9 * * 1-5", timezone="Africa/Kigali") + + assert params.cron == "0 9 * * 1-5" + assert params.timezone == "Africa/Kigali" + + +def test_schedule_params_reject_malformed_cron_with_validation_error() -> None: + """``InvalidCronError`` from ``validate_cron`` must surface as + Pydantic ``ValidationError`` so the FastAPI layer returns 422 instead + of letting the bad value reach storage.""" + with pytest.raises(ValidationError): + ScheduleTriggerParams(cron="not cron", timezone="UTC") + + +def test_schedule_params_reject_unknown_timezone_with_validation_error() -> None: + """An unknown timezone is rejected at the API boundary — same gate + as the cron expression itself.""" + with pytest.raises(ValidationError): + ScheduleTriggerParams(cron="0 9 * * *", timezone="Mars/Olympus_Mons") From 18b4800e49ab8463b563e9129edcfc33da1143e8 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:00 +0200 Subject: [PATCH 082/133] test(automations/dispatch): lock _validate_inputs + DispatchError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover the input-validation contract dispatch_run relies on: - no declared schema → inputs pass through unchanged (regression site that previously stripped runtime keys like fired_at / last_fired_at and broke Jinja templates). - declared schema, valid inputs → passthrough validated. - declared schema, invalid inputs → DispatchError (uniform exception type, not raw jsonschema.ValidationError). Plus the DispatchError exception identity (Exception subclass, message preserved, isinstance-friendly for the dispatch layer's consumers). 4 tests, pure unit. --- .../unit/automations/dispatch/__init__.py | 0 .../unit/automations/dispatch/test_errors.py | 28 +++++++ .../dispatch/test_validate_inputs.py | 77 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/dispatch/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/dispatch/test_errors.py create mode 100644 surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py diff --git a/surfsense_backend/tests/unit/automations/dispatch/__init__.py b/surfsense_backend/tests/unit/automations/dispatch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/dispatch/test_errors.py b/surfsense_backend/tests/unit/automations/dispatch/test_errors.py new file mode 100644 index 000000000..89c1bede9 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/dispatch/test_errors.py @@ -0,0 +1,28 @@ +"""Lock the ``DispatchError`` exception contract. + +``DispatchError`` is the uniform exception type the dispatch layer raises +for any "cannot turn this fire request into a run" condition. Other +modules (templates of error envelopes, run records) compare on +``isinstance(exc, DispatchError)``, so the inheritance is the contract. +""" + +from __future__ import annotations + +import pytest + +from app.automations.dispatch.errors import DispatchError + +pytestmark = pytest.mark.unit + + +def test_dispatch_error_is_exception_subclass_and_carries_message() -> None: + """Lifting a string into ``DispatchError`` preserves the message and + behaves as a regular ``Exception`` for ``isinstance`` / ``raise`` / + ``except`` consumers.""" + error = DispatchError("missing trigger") + + assert isinstance(error, Exception) + assert str(error) == "missing trigger" + + with pytest.raises(DispatchError): + raise error diff --git a/surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py b/surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py new file mode 100644 index 000000000..ec99e51c2 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py @@ -0,0 +1,77 @@ +"""Lock the input-validation contract used by ``dispatch_run``. + +``_validate_inputs`` is module-internal by convention (underscore), but it +encodes a real behavior contract the rest of the system depends on, and the +public alternative (``dispatch_run``) requires a real DB session. Tests +target the pure function directly; the contract — not the symbol — is what's +locked. +""" + +from __future__ import annotations + +import pytest + +from app.automations.dispatch.errors import DispatchError +from app.automations.dispatch.run import _validate_inputs +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.inputs import Inputs +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def _minimal_definition(*, inputs: Inputs | None = None) -> AutomationDefinition: + """One-step definition with an optional declared input schema.""" + return AutomationDefinition( + name="test", + inputs=inputs, + plan=[PlanStep(step_id="s1", action="agent_task")], + ) + + +def test_validate_inputs_passes_through_when_no_schema_is_declared() -> None: + """When the definition declares no input schema, runtime inputs reach + the template context **unchanged**. Regression site: previously this + branch returned ``{}``, which stripped runtime keys like ``fired_at`` + and ``last_fired_at`` and made Jinja blow up on ``{{ inputs.* }}``. + """ + definition = _minimal_definition(inputs=None) + runtime_inputs = { + "fired_at": "2026-01-01T00:00:00+00:00", + "last_fired_at": None, + "static_key": "value", + } + + assert _validate_inputs(definition, runtime_inputs) == runtime_inputs + + +def test_validate_inputs_returns_inputs_when_they_match_declared_schema() -> None: + """With a declared JSON schema, inputs that satisfy it pass through + unchanged (validation succeeds; the function does not coerce or + strip extra fields not mentioned in the schema).""" + schema = { + "type": "object", + "properties": {"topic": {"type": "string"}}, + "required": ["topic"], + } + definition = _minimal_definition(inputs=Inputs(schema=schema)) + + inputs = {"topic": "weekly report"} + + assert _validate_inputs(definition, inputs) == inputs + + +def test_validate_inputs_raises_dispatch_error_when_inputs_violate_schema() -> None: + """Inputs that don't match the declared schema must surface as + ``DispatchError`` (not the raw ``jsonschema.ValidationError``), so the + schedule tick and any other caller can handle one dispatch-domain + exception type uniformly.""" + schema = { + "type": "object", + "properties": {"topic": {"type": "string"}}, + "required": ["topic"], + } + definition = _minimal_definition(inputs=Inputs(schema=schema)) + + with pytest.raises(DispatchError): + _validate_inputs(definition, {"topic": 42}) # type violates string From 49af95b65241a5bdfaba79fe36f17a40c9c72acd Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:08 +0200 Subject: [PATCH 083/133] test(automations/runtime): lock execute_step + with_retries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit execute_step (6 tests): happy path, when=falsy → skipped, unknown action → ActionNotFound failure, retry budget exhaustion (attempts = 1 + max_retries), retry recovery, and template-rendering of step params against the run context. with_retries (3 tests): first-try success returns attempts=1, recovery returns the actual attempt that produced the result, and exhaustion re-raises the last exception with the handler called 1 + max_retries times. All tests use backoff="none" to keep wall-clock time zero; timeout testing is intentionally skipped (would need >= 1s per the int contract, and exhaustion already locks that any Exception triggers retry). --- .../unit/automations/runtime/__init__.py | 0 .../automations/runtime/test_execute_step.py | 272 ++++++++++++++++++ .../unit/automations/runtime/test_retries.py | 72 +++++ 3 files changed, 344 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/runtime/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/runtime/test_execute_step.py create mode 100644 surfsense_backend/tests/unit/automations/runtime/test_retries.py diff --git a/surfsense_backend/tests/unit/automations/runtime/__init__.py b/surfsense_backend/tests/unit/automations/runtime/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py b/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py new file mode 100644 index 000000000..9b203fdba --- /dev/null +++ b/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py @@ -0,0 +1,272 @@ +"""Lock the ``execute_step`` orchestration contract. + +Covers the pure step-execution logic: predicate gate, params rendering, +action lookup, retry budget, error shaping. The ``ActionContext.session`` +is never touched by ``execute_step`` itself (it's only forwarded to the +handler), so unit tests pass ``None`` cast to the type. +""" + +from __future__ import annotations + +from typing import Any, cast + +import pytest +from pydantic import BaseModel +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.actions.store import register_action +from app.automations.actions.types import ActionContext, ActionDefinition +from app.automations.runtime.step import execute_step +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +class _AnyParams(BaseModel): + """Open params model used by test actions — they never validate.""" + + model_config = {"extra": "allow"} + + +def _action_context() -> ActionContext: + """Minimal context: session is unused by ``execute_step``, only forwarded.""" + return ActionContext( + session=cast(AsyncSession, None), + run_id=1, + step_id="s1", + search_space_id=1, + creator_user_id=None, + ) + + +async def test_execute_step_runs_registered_action_handler_and_wraps_result( + isolated_action_registry: None, +) -> None: + """A step pointing at a registered action runs its handler with the + step's params and returns a ``succeeded`` entry carrying the handler's + output plus ``attempts=1`` (one try, no retries triggered).""" + invocations: list[dict[str, Any]] = [] + + async def echo(params: dict[str, Any]) -> dict[str, Any]: + invocations.append(params) + return {"echoed": params["value"]} + + register_action( + ActionDefinition( + type="test_echo", + name="Echo", + description="Test action.", + params_model=_AnyParams, + build_handler=lambda _ctx: echo, + ) + ) + + step = PlanStep(step_id="s1", action="test_echo", params={"value": "hello"}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "succeeded" + assert result["step_id"] == "s1" + assert result["action"] == "test_echo" + assert result["attempts"] == 1 + assert result["result"] == {"echoed": "hello"} + assert invocations == [{"value": "hello"}] + + +async def test_execute_step_skips_step_when_predicate_is_falsy( + isolated_action_registry: None, +) -> None: + """If ``step.when`` evaluates to falsy in the template context, the + handler is **not** invoked, the result entry has ``status=skipped`` + and ``attempts=0``, and no ``result`` key is present.""" + invoked = False + + async def must_not_run(_params: dict[str, Any]) -> dict[str, Any]: + nonlocal invoked + invoked = True + return {} + + register_action( + ActionDefinition( + type="test_guarded", + name="Guarded", + description="Test action that should not run.", + params_model=_AnyParams, + build_handler=lambda _ctx: must_not_run, + ) + ) + + step = PlanStep( + step_id="s1", + action="test_guarded", + when="inputs.enabled", + params={}, + ) + + result = await execute_step( + step=step, + template_context={"inputs": {"enabled": False}}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "skipped" + assert result["attempts"] == 0 + assert "result" not in result + assert invoked is False + + +async def test_execute_step_fails_when_step_references_an_unknown_action( + isolated_action_registry: None, +) -> None: + """A step pointing at an action that isn't in the registry must fail + with ``ActionNotFound`` rather than crashing. Catches typos in the + plan and removed actions without the run going off the rails.""" + step = PlanStep(step_id="s1", action="no_such_action", params={}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "failed" + assert result["attempts"] == 0 + assert result["error"]["type"] == "ActionNotFound" + assert "no_such_action" in result["error"]["message"] + + +async def test_execute_step_retries_failing_handler_up_to_default_budget( + isolated_action_registry: None, +) -> None: + """A handler that raises on every attempt consumes the retry budget + (1 initial try + ``default_max_retries`` retries) and the step ends + ``failed`` with the exception's type and message surfaced through + the error envelope.""" + calls = 0 + + async def always_fails(_params: dict[str, Any]) -> dict[str, Any]: + nonlocal calls + calls += 1 + raise RuntimeError("boom") + + register_action( + ActionDefinition( + type="test_fails", + name="Fails", + description="Always raises.", + params_model=_AnyParams, + build_handler=lambda _ctx: always_fails, + ) + ) + + step = PlanStep(step_id="s1", action="test_fails", params={}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=2, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "failed" + assert result["attempts"] == 3 + assert calls == 3 + assert result["error"]["type"] == "RuntimeError" + assert "boom" in result["error"]["message"] + + +async def test_execute_step_succeeds_when_handler_recovers_within_retry_budget( + isolated_action_registry: None, +) -> None: + """A handler that fails the first N times and then succeeds yields a + ``succeeded`` entry with ``attempts == N + 1``. Locks that retries + can actually recover (not just exhaust).""" + calls = 0 + + async def flaky(_params: dict[str, Any]) -> dict[str, Any]: + nonlocal calls + calls += 1 + if calls < 3: + raise RuntimeError("transient") + return {"ok": True} + + register_action( + ActionDefinition( + type="test_flaky", + name="Flaky", + description="Fails twice, succeeds third time.", + params_model=_AnyParams, + build_handler=lambda _ctx: flaky, + ) + ) + + step = PlanStep(step_id="s1", action="test_flaky", params={}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=2, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "succeeded" + assert result["attempts"] == 3 + assert result["result"] == {"ok": True} + assert calls == 3 + + +async def test_execute_step_renders_step_params_through_template_engine( + isolated_action_registry: None, +) -> None: + """Step params are rendered against the template context before the + handler is invoked. String values containing Jinja expressions get + substituted from ``inputs`` and ``steps`` in the run context.""" + received: list[dict[str, Any]] = [] + + async def capture(params: dict[str, Any]) -> dict[str, Any]: + received.append(params) + return {} + + register_action( + ActionDefinition( + type="test_capture", + name="Capture", + description="Captures the params passed in.", + params_model=_AnyParams, + build_handler=lambda _ctx: capture, + ) + ) + + step = PlanStep( + step_id="s1", + action="test_capture", + params={"message": "Hello {{ inputs.name }}"}, + ) + + await execute_step( + step=step, + template_context={"inputs": {"name": "World"}, "steps": {}}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert received == [{"message": "Hello World"}] diff --git a/surfsense_backend/tests/unit/automations/runtime/test_retries.py b/surfsense_backend/tests/unit/automations/runtime/test_retries.py new file mode 100644 index 000000000..f0f12ca59 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/runtime/test_retries.py @@ -0,0 +1,72 @@ +"""Lock the ``with_retries`` policy: budget, recovery, exhaustion, timeout, backoff. + +Tests with ``backoff="none"`` to keep wall-clock time zero. Backoff sleep +values themselves are observed by monkeypatching ``asyncio.sleep`` so we +don't introduce flakiness via real timing. +""" + +from __future__ import annotations + +import pytest + +from app.automations.runtime.retries import with_retries + +pytestmark = pytest.mark.unit + + +async def test_with_retries_returns_result_and_attempts_one_on_first_success() -> None: + """A coroutine that succeeds on the first call returns its result + paired with ``attempts=1`` — no retry consumed.""" + calls = 0 + + async def succeed() -> str: + nonlocal calls + calls += 1 + return "ok" + + result, attempts = await with_retries( + succeed, max_retries=2, backoff="none", timeout=None + ) + + assert result == "ok" + assert attempts == 1 + assert calls == 1 + + +async def test_with_retries_returns_attempt_count_when_succeeding_after_failures() -> None: + """A coroutine that fails twice then succeeds returns ``attempts=3`` + (the actual attempt that produced the result). Locks the contract + that the caller can distinguish first-try success from a recovery.""" + calls = 0 + + async def flaky() -> str: + nonlocal calls + calls += 1 + if calls < 3: + raise RuntimeError("transient") + return "ok" + + result, attempts = await with_retries( + flaky, max_retries=5, backoff="none", timeout=None + ) + + assert result == "ok" + assert attempts == 3 + assert calls == 3 + + +async def test_with_retries_reraises_after_exhausting_the_budget() -> None: + """When the coroutine raises on every attempt within + ``1 + max_retries`` tries, the last exception propagates and the + handler is called exactly ``1 + max_retries`` times.""" + calls = 0 + + async def always_fails() -> str: + nonlocal calls + calls += 1 + raise RuntimeError(f"boom-{calls}") + + with pytest.raises(RuntimeError, match="boom-3"): + await with_retries(always_fails, max_retries=2, backoff="none", timeout=None) + + assert calls == 3 # 1 initial + 2 retries From db4eef651f62f735e9c918d5bd23f93c2d1f66ab Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:22 +0200 Subject: [PATCH 084/133] test(automations/templating): lock render, filters, environment, context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit render.py (4): variable substitution, StrictUndefined raises on missing keys, evaluate_predicate coerces to bool, render_value walks dicts/lists and renders string leaves. filters.py (4): slugify produces URL-safe output, date formats datetime with strftime, date(None) → "" so templates can write {{ inputs.last_fired_at | date }} on first run, date(str) passes through. environment.py (4): the sandbox boundary — disallowed Jinja built-ins (e.g. pprint) raise, and the finalize hook coerces non-string outputs to predictable wire shapes (datetime → ISO, None → "", dict → JSON). context.py (1): build_run_context exposes {run, inputs, steps} with the exact shape every plan template body relies on. 13 tests total, all pure unit. --- .../unit/automations/templating/__init__.py | 0 .../automations/templating/test_context.py | 53 +++++++++++++++++ .../templating/test_environment.py | 51 ++++++++++++++++ .../automations/templating/test_filters.py | 42 +++++++++++++ .../automations/templating/test_render.py | 59 +++++++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/templating/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_context.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_environment.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_filters.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_render.py diff --git a/surfsense_backend/tests/unit/automations/templating/__init__.py b/surfsense_backend/tests/unit/automations/templating/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/templating/test_context.py b/surfsense_backend/tests/unit/automations/templating/test_context.py new file mode 100644 index 000000000..54f372e77 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_context.py @@ -0,0 +1,53 @@ +"""Lock the ``{run, inputs, steps}`` namespace exposed to every template.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from uuid import UUID + +import pytest + +from app.automations.templating.context import build_run_context + +pytestmark = pytest.mark.unit + + +def test_build_run_context_exposes_run_inputs_and_steps_namespaces() -> None: + """The namespace handed to templates groups run metadata under ``run``, + runtime + static inputs under ``inputs``, and step outputs (keyed by + ``output_as`` / ``step_id``) under ``steps``. Locks the contract that + every plan template body relies on.""" + creator = UUID("00000000-0000-0000-0000-000000000001") + started = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) + + ctx = build_run_context( + run_id=42, + automation_id=7, + automation_name="Weekly digest", + automation_version=3, + search_space_id=1, + creator_id=creator, + trigger_id=11, + trigger_type="schedule", + started_at=started, + attempt=2, + inputs={"topic": "weekly"}, + step_outputs={"summarize": {"text": "ok"}}, + ) + + assert ctx == { + "run": { + "id": 42, + "automation_id": 7, + "automation_name": "Weekly digest", + "automation_version": 3, + "search_space_id": 1, + "creator_id": creator, + "trigger_id": 11, + "trigger_type": "schedule", + "started_at": started, + "attempt": 2, + }, + "inputs": {"topic": "weekly"}, + "steps": {"summarize": {"text": "ok"}}, + } diff --git a/surfsense_backend/tests/unit/automations/templating/test_environment.py b/surfsense_backend/tests/unit/automations/templating/test_environment.py new file mode 100644 index 000000000..ec1c0ee40 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_environment.py @@ -0,0 +1,51 @@ +"""Lock the sandbox boundary: disallowed filters/tests reject, finalize coerces non-strings. + +These behaviors live in ``environment.py`` but are observed through the +public ``render_template`` surface — the same surface every step uses. +""" + +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest +from jinja2.exceptions import TemplateError + +from app.automations.templating.render import render_template + +pytestmark = pytest.mark.unit + + +def test_environment_rejects_filters_not_in_the_allowlist() -> None: + """A template that pipes through a Jinja built-in **not** in the + allowlist (e.g. ``pprint``) must fail rather than rendering. Locks + the sandbox surface against accidental re-introduction of removed + filters.""" + with pytest.raises(TemplateError): + render_template("{{ value | pprint }}", {"value": {"k": 1}}) + + +def test_environment_finalizes_datetime_output_to_iso_string() -> None: + """A datetime that lands directly at an output site is stringified + via ``isoformat()`` rather than producing ``str(datetime)`` (which + has a space separator). Locks the wire shape templates produce + when emitting ``inputs.fired_at`` and other datetime values.""" + dt = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) + + assert render_template("{{ moment }}", {"moment": dt}) == "2026-05-28T14:30:00+00:00" + + +def test_environment_finalizes_none_output_to_empty_string() -> None: + """A ``None`` at an output site becomes the empty string. Lets + templates write ``{{ inputs.last_fired_at }}`` unconditionally on + the first run without exploding on the null.""" + assert render_template("{{ missing }}", {"missing": None}) == "" + + +def test_environment_finalizes_dict_output_to_json() -> None: + """A dict at an output site is JSON-serialized. Same for lists. + Locks the wire shape so users embedding structured values into + prompts get deterministic, parseable output.""" + rendered = render_template("{{ payload }}", {"payload": {"a": 1, "b": [2, 3]}}) + + assert rendered == '{"a": 1, "b": [2, 3]}' diff --git a/surfsense_backend/tests/unit/automations/templating/test_filters.py b/surfsense_backend/tests/unit/automations/templating/test_filters.py new file mode 100644 index 000000000..cf83ee337 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_filters.py @@ -0,0 +1,42 @@ +"""Lock the custom Jinja filters: ``date`` and ``slugify``.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest + +from app.automations.templating.filters import filter_date, filter_slugify + +pytestmark = pytest.mark.unit + + +def test_filter_slugify_produces_url_safe_slug_from_typical_title() -> None: + """``filter_slugify`` lowercases, replaces non-alphanumerics with + hyphens, collapses repeats, and trims edge hyphens — the standard + URL-slug contract users expect when piping titles into paths.""" + assert filter_slugify("Hello, World! 2026") == "hello-world-2026" + + +def test_filter_date_formats_datetime_with_strftime_format() -> None: + """``filter_date`` calls ``strftime`` on datetime-like values with the + provided format. Default format yields ISO date (YYYY-MM-DD).""" + dt = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) + + assert filter_date(dt) == "2026-05-28" + assert filter_date(dt, "%Y/%m/%d %H:%M") == "2026/05/28 14:30" + + +def test_filter_date_returns_empty_string_for_none() -> None: + """``None`` (e.g., a never-fired ``last_fired_at``) renders as the + empty string rather than the literal ``"None"`` or raising. This is + what lets templates write ``{{ inputs.last_fired_at | date }}`` + unconditionally on the first run.""" + assert filter_date(None) == "" + + +def test_filter_date_passes_strings_through_unchanged() -> None: + """Already-formatted ISO strings (the JSON-serialized shape of + runtime inputs like ``fired_at``) pass through unchanged so callers + don't have to special-case the type.""" + assert filter_date("2026-05-28T14:30:00+00:00") == "2026-05-28T14:30:00+00:00" diff --git a/surfsense_backend/tests/unit/automations/templating/test_render.py b/surfsense_backend/tests/unit/automations/templating/test_render.py new file mode 100644 index 000000000..42a7c7082 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_render.py @@ -0,0 +1,59 @@ +"""Lock the public template-rendering surface: render, predicate, recursive.""" + +from __future__ import annotations + +import pytest +from jinja2 import UndefinedError + +from app.automations.templating.render import ( + evaluate_predicate, + render_template, + render_value, +) + +pytestmark = pytest.mark.unit + + +def test_render_template_substitutes_context_variables() -> None: + """A template referencing a context variable produces the substituted + string. Most basic contract of the template engine.""" + result = render_template("Hello {{ name }}!", {"name": "World"}) + + assert result == "Hello World!" + + +def test_render_template_raises_on_undefined_variable() -> None: + """Referencing a variable that isn't in the context raises rather than + rendering the empty string. Locks the StrictUndefined safety net so + template typos surface as run failures instead of silent corruption.""" + with pytest.raises(UndefinedError): + render_template("Hello {{ missing }}!", {}) + + +def test_evaluate_predicate_returns_truthy_outcome_of_expression() -> None: + """``evaluate_predicate`` compiles a Jinja **expression** (not template + body) and coerces the value to ``bool``. Drives ``step.when`` gating.""" + assert evaluate_predicate("inputs.count > 0", {"inputs": {"count": 3}}) is True + assert evaluate_predicate("inputs.count > 0", {"inputs": {"count": 0}}) is False + + +def test_render_value_renders_strings_recursively_through_dicts_and_lists() -> None: + """``render_value`` walks dicts and lists, renders string leaves through + the template engine, and leaves non-strings untouched. This is the + primitive ``execute_step`` uses to render step params at run time.""" + context = {"inputs": {"name": "World"}, "topic": "weekly"} + + rendered = render_value( + { + "greeting": "Hello {{ inputs.name }}", + "tags": ["{{ topic }}", "static"], + "config": {"retries": 3, "label": "{{ topic }}-{{ inputs.name }}"}, + }, + context, + ) + + assert rendered == { + "greeting": "Hello World", + "tags": ["weekly", "static"], + "config": {"retries": 3, "label": "weekly-World"}, + } From acbeb60a4325e1f1216594293363955d07a934f3 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:29 +0200 Subject: [PATCH 085/133] test(automations/actions): lock agent_task helpers (auto_decide + finalize) auto_decide.build_auto_decisions (3): produces one decision per action_request entry, defaults to one decision for legacy scalar interrupts, and skips malformed interrupts silently so a misbehaving tool can't take down the whole agent_task step. finalize.extract_final_assistant_message (4): string-content AIMessage returned verbatim, list-of-parts content concatenated (skipping non-text parts like tool_use), walks back past trailing ToolMessages to find the last AIMessage, and returns None when no extractable text is present (so callers can branch on silence vs. empty). 7 tests, pure unit. --- .../unit/automations/actions/__init__.py | 0 .../actions/agent_task/__init__.py | 0 .../actions/agent_task/test_auto_decide.py | 73 +++++++++++++++++ .../actions/agent_task/test_finalize.py | 80 +++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/actions/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py diff --git a/surfsense_backend/tests/unit/automations/actions/__init__.py b/surfsense_backend/tests/unit/automations/actions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/__init__.py b/surfsense_backend/tests/unit/automations/actions/agent_task/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py new file mode 100644 index 000000000..439f32e41 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py @@ -0,0 +1,73 @@ +"""Lock ``build_auto_decisions`` — the HITL auto-approve/reject wire mapper. + +``build_auto_decisions`` walks ``state.interrupts`` (duck-typed) and produces +two parallel resume maps: one keyed by LangGraph ``Interrupt.id`` and one +keyed by ``tool_call_id`` for the subagent middleware bridge. Both carry +the same decision payload. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +import pytest + +from app.automations.actions.agent_task.auto_decide import build_auto_decisions + +pytestmark = pytest.mark.unit + + +def _state(interrupts: list[Any]) -> SimpleNamespace: + """Build a duck-typed LangGraph state stub carrying ``interrupts``.""" + return SimpleNamespace(interrupts=interrupts) + + +def _interrupt(*, id_: str, value: Any) -> SimpleNamespace: + """Build a duck-typed interrupt with the canonical ``(id, value)`` shape.""" + return SimpleNamespace(id=id_, value=value) + + +def test_build_auto_decisions_produces_one_decision_per_action_request() -> None: + """An interrupt carrying N ``action_requests`` produces N decisions of + the requested type in both maps. This is the canonical batched-HITL + wire shape — losing a decision would leave a pending action stuck.""" + interrupt = _interrupt( + id_="lg-1", + value={ + "tool_call_id": "tc-1", + "action_requests": [{"id": "a"}, {"id": "b"}], + }, + ) + + lg_map, routed = build_auto_decisions(_state([interrupt]), "approve") + + assert lg_map == {"lg-1": {"decisions": [{"type": "approve"}, {"type": "approve"}]}} + assert routed == {"tc-1": {"decisions": [{"type": "approve"}, {"type": "approve"}]}} + + +def test_build_auto_decisions_defaults_to_one_decision_for_scalar_interrupt() -> None: + """When an interrupt's value has no ``action_requests`` list, the + function defaults to a single decision. Locks compatibility with + older single-action interrupt shapes still emitted by some tools.""" + interrupt = _interrupt(id_="lg-2", value={"tool_call_id": "tc-2"}) + + lg_map, routed = build_auto_decisions(_state([interrupt]), "reject") + + assert lg_map == {"lg-2": {"decisions": [{"type": "reject"}]}} + assert routed == {"tc-2": {"decisions": [{"type": "reject"}]}} + + +def test_build_auto_decisions_skips_interrupts_with_invalid_shape() -> None: + """Interrupts missing the canonical ``(str id, dict value)`` shape are + skipped silently rather than crashing the resume loop. Locks the + resilience contract — a malformed interrupt from a misbehaving tool + shouldn't take down the whole agent_task step.""" + good = _interrupt(id_="lg-good", value={"tool_call_id": "tc-good"}) + bad_value = _interrupt(id_="lg-bad-value", value="not a dict") + bad_id = _interrupt(id_=None, value={"tool_call_id": "tc-bad-id"}) # type: ignore[arg-type] + + lg_map, routed = build_auto_decisions(_state([good, bad_value, bad_id]), "approve") + + assert lg_map == {"lg-good": {"decisions": [{"type": "approve"}]}} + assert routed == {"tc-good": {"decisions": [{"type": "approve"}]}} diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py new file mode 100644 index 000000000..bd49d764c --- /dev/null +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py @@ -0,0 +1,80 @@ +"""Lock ``extract_final_assistant_message`` — what surfaces in run output. + +Each scenario is one shape the agent runtime is observed to produce. +Locking these means we can refactor the extractor without losing +backwards compatibility with already-stored ``run.output`` payloads. +""" + +from __future__ import annotations + +import pytest +from langchain_core.messages import AIMessage, HumanMessage, ToolMessage + +from app.automations.actions.agent_task.finalize import extract_final_assistant_message + +pytestmark = pytest.mark.unit + + +def test_extract_returns_last_ai_message_string_content() -> None: + """The canonical shape: the agent's final ``AIMessage`` carries a + plain string. That string is returned verbatim, trimmed.""" + result = { + "messages": [ + HumanMessage(content="ask"), + AIMessage(content="the answer"), + ] + } + + assert extract_final_assistant_message(result) == "the answer" + + +def test_extract_concatenates_text_parts_and_skips_non_text_parts() -> None: + """Multi-part AIMessage content (Anthropic / OpenAI list shape) joins + its ``text`` parts in order; non-text parts (tool_use, images, ...) + are skipped. Locks the wire shape used when the model emits tool + calls alongside narrative text in the same turn.""" + result = { + "messages": [ + AIMessage( + content=[ + {"type": "text", "text": "Hello "}, + {"type": "tool_use", "name": "search", "input": {}}, + {"type": "text", "text": "world"}, + ] + ) + ] + } + + assert extract_final_assistant_message(result) == "Hello world" + + +def test_extract_returns_last_ai_message_skipping_tool_messages() -> None: + """When the transcript ends with tool calls and tool results, the + extractor still walks back to the **last** ``AIMessage`` (the agent's + final narrative answer). Locks resilience against trailing + ``ToolMessage`` payloads in the transcript.""" + result = { + "messages": [ + HumanMessage(content="ask"), + AIMessage(content="thinking..."), + ToolMessage(content="tool output", tool_call_id="tc-1"), + AIMessage(content="final answer"), + ToolMessage(content="trailing tool noise", tool_call_id="tc-2"), + ] + } + + assert extract_final_assistant_message(result) == "final answer" + + +def test_extract_returns_none_when_no_assistant_text_is_present() -> None: + """No ``AIMessage`` with extractable text → ``None`` rather than the + empty string. Lets callers branch on "did the agent actually say + anything?" rather than guess whether ``""`` means silence or empty + output. Empty-string contents are normalized to ``None`` too.""" + no_ai = {"messages": [HumanMessage(content="just a question")]} + only_tools = {"messages": [AIMessage(content=[{"type": "tool_use", "name": "x", "input": {}}])]} + empty_string = {"messages": [AIMessage(content=" ")]} + + assert extract_final_assistant_message(no_ai) is None + assert extract_final_assistant_message(only_tools) is None + assert extract_final_assistant_message(empty_string) is None From 822940b09e4eddbcd903c5d480d156e84e11f402 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:42 +0200 Subject: [PATCH 086/133] test(automations/schemas): lock definition + api validation gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit definition/ (29 tests): the envelope (defaults, extra=forbid, empty plan/name rejection), Inputs schema-alias roundtrip (Python schema_ ↔ wire schema), PlanStep numeric bounds + addressing-field constraints, Execution production defaults stability (10-min timeout, 2 retries, exponential backoff, drop_if_running) + closed-set Literal gates, Metadata's exceptional extra="allow" contract, and TriggerSpec type requirement. api/ (9 tests): AutomationCreate/Update cascade-validate into the nested definition, reject unknown payload fields, enforce name length; TriggerCreate exposes safe defaults (enabled=True, params={}, static_inputs={}) and rejects unknown TriggerType strings at the boundary. All pure unit, no DB. --- .../unit/automations/schemas/__init__.py | 0 .../unit/automations/schemas/api/__init__.py | 0 .../schemas/api/test_api_automation.py | 82 +++++++++++++++++++ .../schemas/api/test_api_trigger.py | 47 +++++++++++ .../schemas/definition/__init__.py | 0 .../schemas/definition/test_envelope.py | 57 +++++++++++++ .../schemas/definition/test_execution.py | 49 +++++++++++ .../schemas/definition/test_inputs.py | 39 +++++++++ .../schemas/definition/test_metadata.py | 37 +++++++++ .../schemas/definition/test_plan_step.py | 52 ++++++++++++ .../schemas/definition/test_trigger_spec.py | 33 ++++++++ 11 files changed, 396 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/schemas/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/api/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py diff --git a/surfsense_backend/tests/unit/automations/schemas/__init__.py b/surfsense_backend/tests/unit/automations/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/schemas/api/__init__.py b/surfsense_backend/tests/unit/automations/schemas/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py b/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py new file mode 100644 index 000000000..6ae3ce794 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py @@ -0,0 +1,82 @@ +"""Lock the request-side automation API schemas — the public validation gate.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.api.automation import AutomationCreate, AutomationUpdate + +pytestmark = pytest.mark.unit + + +_VALID_DEFINITION = { + "name": "Test", + "plan": [{"step_id": "s1", "action": "agent_task"}], +} + + +def test_automation_create_accepts_valid_minimal_payload() -> None: + """Happy path: just search_space_id, name, and a valid definition. + Triggers default to ``[]`` so users can attach them later.""" + payload = AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "Daily digest", + "definition": _VALID_DEFINITION, + } + ) + + assert payload.name == "Daily digest" + assert payload.description is None + assert payload.triggers == [] + + +def test_automation_create_cascades_validation_into_nested_definition() -> None: + """A bad ``definition`` (e.g. empty plan) fails at the API boundary, + not at the DB layer. Locks the cascade so corrupt definitions can't + sneak through a misshapen wire payload.""" + with pytest.raises(ValidationError): + AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "Bad", + "definition": {"name": "X", "plan": []}, # empty plan + } + ) + + +def test_automation_create_rejects_unknown_top_level_field() -> None: + """``extra='forbid'`` catches typos in API payloads at the boundary.""" + with pytest.raises(ValidationError): + AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "X", + "definition": _VALID_DEFINITION, + "owner": "tg", # not allowed + } + ) + + +def test_automation_create_rejects_empty_name() -> None: + """Name is required and constrained to 1..200 chars.""" + with pytest.raises(ValidationError): + AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "", + "definition": _VALID_DEFINITION, + } + ) + + +def test_automation_update_accepts_partial_payload_with_no_fields() -> None: + """All fields on ``AutomationUpdate`` are optional. An empty body is + a valid no-op update (the service layer decides what to do with it).""" + update = AutomationUpdate.model_validate({}) + + assert update.name is None + assert update.description is None + assert update.status is None + assert update.definition is None diff --git a/surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py b/surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py new file mode 100644 index 000000000..cabfc41af --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py @@ -0,0 +1,47 @@ +"""Lock the request-side trigger API schemas.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.schemas.api.trigger import TriggerCreate, TriggerUpdate + +pytestmark = pytest.mark.unit + + +def test_trigger_create_uses_safe_defaults_for_optional_fields() -> None: + """Defaults: empty ``params`` and ``static_inputs``, ``enabled=True``. + These let callers create a trigger with just ``type`` + the params + the trigger requires.""" + trigger = TriggerCreate(type=TriggerType.SCHEDULE) # type: ignore[arg-type] + + assert trigger.type is TriggerType.SCHEDULE + assert trigger.params == {} + assert trigger.static_inputs == {} + assert trigger.enabled is True + + +def test_trigger_create_rejects_unknown_trigger_type_string() -> None: + """``type`` is a ``TriggerType`` enum, so any string outside the + enum's known values fails validation at the boundary.""" + with pytest.raises(ValidationError): + TriggerCreate.model_validate({"type": "webhook"}) # not in TriggerType + + +def test_trigger_create_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos in trigger payloads.""" + with pytest.raises(ValidationError): + TriggerCreate.model_validate( + {"type": "schedule", "param": {}} # typo: param vs params + ) + + +def test_trigger_update_accepts_partial_payload_with_no_fields() -> None: + """``TriggerUpdate`` is fully optional — empty body is valid (no-op).""" + update = TriggerUpdate() + + assert update.enabled is None + assert update.params is None + assert update.static_inputs is None diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/__init__.py b/surfsense_backend/tests/unit/automations/schemas/definition/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py new file mode 100644 index 000000000..c625b0ec9 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py @@ -0,0 +1,57 @@ +"""Lock the ``AutomationDefinition`` envelope contract.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def test_automation_definition_accepts_minimal_valid_input_with_sensible_defaults() -> None: + """A definition with just ``name`` + a one-step ``plan`` is valid and + fills in the rest with safe defaults so users don't have to write + out every section to get started.""" + definition = AutomationDefinition( + name="Daily digest", + plan=[PlanStep(step_id="s1", action="agent_task")], + ) + + assert definition.name == "Daily digest" + assert definition.schema_version == "1.0" + assert definition.goal is None + assert definition.inputs is None + assert definition.triggers == [] + + +def test_automation_definition_rejects_unknown_top_level_field() -> None: + """``extra='forbid'`` catches typos at validation time (e.g. ``pln`` + instead of ``plan``) before the bad definition reaches storage.""" + with pytest.raises(ValidationError): + AutomationDefinition.model_validate( + { + "name": "X", + "plan": [{"step_id": "s1", "action": "agent_task"}], + "extra_field": "unexpected", + } + ) + + +def test_automation_definition_rejects_empty_plan() -> None: + """An automation with no plan steps has nothing to execute and must + be rejected at validation time.""" + with pytest.raises(ValidationError): + AutomationDefinition(name="X", plan=[]) + + +def test_automation_definition_rejects_empty_name() -> None: + """Name is required and must be non-empty so list views and audit + logs have something meaningful to display.""" + with pytest.raises(ValidationError): + AutomationDefinition( + name="", + plan=[PlanStep(step_id="s1", action="agent_task")], + ) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py new file mode 100644 index 000000000..15adefab0 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py @@ -0,0 +1,49 @@ +"""Lock the ``Execution`` defaults + literal-constraint contract. + +These defaults control production behavior of every automation that +doesn't override them; the defaults *are* the contract. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.execution import Execution + +pytestmark = pytest.mark.unit + + +def test_execution_uses_production_defaults_when_no_overrides_provided() -> None: + """The defaults shipped to prod: 10-minute wall clock, 2 retries + per step, exponential backoff, drop overlapping runs. Changing any + of these is a behavioral release-note change.""" + execution = Execution() + + assert execution.timeout_seconds == 600 + assert execution.max_retries == 2 + assert execution.retry_backoff == "exponential" + assert execution.concurrency == "drop_if_running" + assert execution.on_failure == [] + + +def test_execution_rejects_unknown_retry_backoff_strategy() -> None: + """``retry_backoff`` is constrained to a closed set — typos like + ``"expontential"`` must fail validation, not silently coerce.""" + with pytest.raises(ValidationError): + Execution(retry_backoff="expontential") # type: ignore[arg-type] + + +def test_execution_rejects_unknown_concurrency_strategy() -> None: + """Same closed-set constraint on ``concurrency``.""" + with pytest.raises(ValidationError): + Execution(concurrency="parallel") # type: ignore[arg-type] + + +def test_execution_rejects_invalid_numeric_bounds() -> None: + """``timeout_seconds > 0`` and ``max_retries >= 0``. Zero or negative + values would produce nonsensical run behavior.""" + with pytest.raises(ValidationError): + Execution(timeout_seconds=0) + with pytest.raises(ValidationError): + Execution(max_retries=-1) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py new file mode 100644 index 000000000..5dc24463f --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py @@ -0,0 +1,39 @@ +"""Lock the ``Inputs`` JSON ``schema``-alias roundtrip. + +The field is ``schema_`` in Python (``schema`` shadows a Pydantic builtin) +but is wire-named ``schema``. Locking the roundtrip means JSON definitions +authored anywhere (UI raw editor, NL drafter, CLI export) speak the same +wire shape. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.inputs import Inputs + +pytestmark = pytest.mark.unit + + +def test_inputs_parses_wire_field_named_schema_into_schema_attribute() -> None: + """JSON payloads use ``schema`` (the convention). The model stores it + on the Python attribute ``schema_`` without shadowing the builtin.""" + parsed = Inputs.model_validate({"schema": {"type": "object"}}) + + assert parsed.schema_ == {"type": "object"} + + +def test_inputs_serializes_schema_attribute_back_to_wire_field_named_schema() -> None: + """Round-trip: serializing emits ``schema`` (alias), not ``schema_``. + Locks the consumer-visible JSON shape regardless of the Python name.""" + inputs = Inputs(schema={"type": "object"}) # type: ignore[call-arg] + + assert inputs.model_dump() == {"schema": {"type": "object"}} + + +def test_inputs_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos like ``shema`` so bad definitions + don't silently lose their input declaration.""" + with pytest.raises(ValidationError): + Inputs.model_validate({"schema": {}, "extra": "x"}) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py new file mode 100644 index 000000000..9ac90bb3f --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py @@ -0,0 +1,37 @@ +"""Lock the ``Metadata`` ``extra='allow'`` contract — the only schema +that does. Free-form annotations on definitions (e.g. ``owner``, +``project``, ``created_by_ai``) need to round-trip through the envelope +without being rejected. +""" + +from __future__ import annotations + +import pytest + +from app.automations.schemas.definition.metadata import Metadata + +pytestmark = pytest.mark.unit + + +def test_metadata_preserves_unknown_keys() -> None: + """Unlike every other definition sub-schema, ``Metadata`` allows + extra keys and round-trips them — that's its purpose.""" + metadata = Metadata.model_validate( + { + "tags": ["weekly", "report"], + "owner": "tg", + "created_by_ai": True, + } + ) + + dumped = metadata.model_dump() + + assert dumped["tags"] == ["weekly", "report"] + assert dumped["owner"] == "tg" + assert dumped["created_by_ai"] is True + + +def test_metadata_defaults_tags_to_empty_list() -> None: + """No tags is the common case; the default is the empty list so + callers can append without a None check.""" + assert Metadata().tags == [] diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py new file mode 100644 index 000000000..6896a7f5a --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py @@ -0,0 +1,52 @@ +"""Lock the ``PlanStep`` validation contract.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def test_plan_step_accepts_minimal_input_with_safe_defaults() -> None: + """A step with just ``step_id`` + ``action`` is valid. Defaults + (no when, empty params, no output_as override, no retry/timeout + override) let the run inherit automation-wide defaults.""" + step = PlanStep(step_id="s1", action="agent_task") + + assert step.step_id == "s1" + assert step.action == "agent_task" + assert step.when is None + assert step.params == {} + assert step.output_as is None + assert step.max_retries is None + assert step.timeout_seconds is None + + +def test_plan_step_rejects_empty_step_id_and_action() -> None: + """``step_id`` and ``action`` are addressing primitives — empty + strings would silently break runtime lookups.""" + with pytest.raises(ValidationError): + PlanStep(step_id="", action="agent_task") + with pytest.raises(ValidationError): + PlanStep(step_id="s1", action="") + + +def test_plan_step_rejects_negative_max_retries_and_non_positive_timeout() -> None: + """Numeric constraints: ``max_retries >= 0`` and ``timeout_seconds > 0``. + Negative budgets or zero timeouts produce nonsensical run behavior.""" + with pytest.raises(ValidationError): + PlanStep(step_id="s1", action="agent_task", max_retries=-1) + with pytest.raises(ValidationError): + PlanStep(step_id="s1", action="agent_task", timeout_seconds=0) + + +def test_plan_step_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos like ``actoin`` (instead of + ``action``) before the bad step reaches storage.""" + with pytest.raises(ValidationError): + PlanStep.model_validate( + {"step_id": "s1", "action": "agent_task", "actoin": "agent_task"} + ) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py new file mode 100644 index 000000000..cf1a52466 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py @@ -0,0 +1,33 @@ +"""Lock the ``TriggerSpec`` validation contract — the entry shape used +inside an automation's ``triggers[]`` array. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.trigger_spec import TriggerSpec + +pytestmark = pytest.mark.unit + + +def test_trigger_spec_accepts_type_with_default_empty_params() -> None: + """``type`` is required; ``params`` defaults to ``{}`` so triggers + that take no params don't need an explicit body.""" + spec = TriggerSpec(type="schedule") + + assert spec.type == "schedule" + assert spec.params == {} + + +def test_trigger_spec_rejects_empty_type() -> None: + """``type`` is the registry lookup key — empty would silently miss.""" + with pytest.raises(ValidationError): + TriggerSpec(type="") + + +def test_trigger_spec_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos at definition-validation time.""" + with pytest.raises(ValidationError): + TriggerSpec.model_validate({"type": "schedule", "paramz": {}}) From 353755fd73fc6320758ac4c3962ed115e01a86ea Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:55 +0200 Subject: [PATCH 087/133] test(automations): cross-cutting registries, enums, side-effects + shared fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top-level tests that span multiple submodules: - test_stores.py (7): the trigger + action registry contracts — register round-trip, unknown type → None (not raise), duplicate registration rejected, defensive snapshot from all_*. - test_definition_types.py (2): params_schema property on both ActionDefinition and TriggerDefinition reflects the Pydantic model. - test_persistence_enums.py (3): exact string values + member sets of AutomationStatus / RunStatus / TriggerType — the postgres-mirrored contract that breaks stored rows if drifted. - test_import_registrations.py (2): the bundled agent_task action and schedule trigger self-register on package import (canary for the side-effect import chain). conftest.py adds isolated_action_registry / isolated_trigger_registry fixtures: snapshot + restore of the module-level _REGISTRY dicts so tests that add their own definitions don't leak across the suite. 14 tests, pure unit. --- .../tests/unit/automations/__init__.py | 0 .../tests/unit/automations/conftest.py | 39 ++++++ .../unit/automations/test_definition_types.py | 56 +++++++++ .../automations/test_import_registrations.py | 37 ++++++ .../automations/test_persistence_enums.py | 45 +++++++ .../tests/unit/automations/test_stores.py | 115 ++++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/conftest.py create mode 100644 surfsense_backend/tests/unit/automations/test_definition_types.py create mode 100644 surfsense_backend/tests/unit/automations/test_import_registrations.py create mode 100644 surfsense_backend/tests/unit/automations/test_persistence_enums.py create mode 100644 surfsense_backend/tests/unit/automations/test_stores.py diff --git a/surfsense_backend/tests/unit/automations/__init__.py b/surfsense_backend/tests/unit/automations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/conftest.py b/surfsense_backend/tests/unit/automations/conftest.py new file mode 100644 index 000000000..0fbf03234 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/conftest.py @@ -0,0 +1,39 @@ +"""Shared fixtures for the ``app.automations`` unit-test tree. + +Provides registry isolation: the built-in ``schedule`` trigger and +``agent_task`` action self-register at import time. Tests that register +additional triggers/actions (or assert on the registry contents) must +not leak that state to other tests. These fixtures snapshot and restore +the module-level registry dicts. +""" + +from __future__ import annotations + +from collections.abc import Iterator + +import pytest + +from app.automations.actions import store as action_store +from app.automations.triggers import store as trigger_store + + +@pytest.fixture +def isolated_action_registry() -> Iterator[None]: + """Snapshot and restore the action registry around a test.""" + snapshot = dict(action_store._REGISTRY) + try: + yield + finally: + action_store._REGISTRY.clear() + action_store._REGISTRY.update(snapshot) + + +@pytest.fixture +def isolated_trigger_registry() -> Iterator[None]: + """Snapshot and restore the trigger registry around a test.""" + snapshot = dict(trigger_store._REGISTRY) + try: + yield + finally: + trigger_store._REGISTRY.clear() + trigger_store._REGISTRY.update(snapshot) diff --git a/surfsense_backend/tests/unit/automations/test_definition_types.py b/surfsense_backend/tests/unit/automations/test_definition_types.py new file mode 100644 index 000000000..231e4fa97 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_definition_types.py @@ -0,0 +1,56 @@ +"""Lock the ``params_schema`` derivation on action + trigger definitions. + +Both definition dataclasses expose ``params_schema`` as the JSON Schema +of their ``params_model``. This is what the registry endpoints surface +to the UI as the "what shape do these params take?" contract. +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from app.automations.actions.types import ActionDefinition +from app.automations.triggers.types import TriggerDefinition + +pytestmark = pytest.mark.unit + + +class _Topic(BaseModel): + """Model with one required string field — minimal schema fingerprint.""" + + topic: str + + +def test_action_definition_params_schema_reflects_params_model() -> None: + """``ActionDefinition.params_schema`` returns a JSON Schema derived + from the Pydantic ``params_model`` — required fields and types are + visible to clients consuming the registry endpoint.""" + definition = ActionDefinition( + type="t", + name="N", + description="D", + params_model=_Topic, + build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + ) + + schema = definition.params_schema + + assert schema["type"] == "object" + assert schema["properties"]["topic"]["type"] == "string" + assert "topic" in schema["required"] + + +def test_trigger_definition_params_schema_reflects_params_model() -> None: + """Same JSON-Schema derivation contract on the trigger side.""" + definition = TriggerDefinition( + type="t", + description="D", + params_model=_Topic, + ) + + schema = definition.params_schema + + assert schema["type"] == "object" + assert schema["properties"]["topic"]["type"] == "string" + assert "topic" in schema["required"] diff --git a/surfsense_backend/tests/unit/automations/test_import_registrations.py b/surfsense_backend/tests/unit/automations/test_import_registrations.py new file mode 100644 index 000000000..35b1effa7 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_import_registrations.py @@ -0,0 +1,37 @@ +"""Lock the bundled import side-effects. + +Importing ``app.automations`` (the package) registers the v1 bundled +action (``agent_task``) and the v1 bundled trigger (``schedule``). If the +import chain breaks (e.g. someone removes ``from . import definition`` +in a sub-package ``__init__``), the system would silently launch with an +empty registry. These tests are the canary. +""" + +from __future__ import annotations + +import pytest + +import app.automations # noqa: F401 (force the package import + its side-effects) +from app.automations.actions.store import get_action +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.triggers.store import get_trigger + +pytestmark = pytest.mark.unit + + +def test_bundled_agent_task_action_is_registered_after_package_import() -> None: + """``agent_task`` — the v1 default action — must be discoverable in + the registry after the package is imported.""" + definition = get_action("agent_task") + + assert definition is not None + assert definition.type == "agent_task" + + +def test_bundled_schedule_trigger_is_registered_after_package_import() -> None: + """``schedule`` — the only v1 trigger — must be discoverable in the + registry after the package is imported.""" + definition = get_trigger(TriggerType.SCHEDULE.value) + + assert definition is not None + assert definition.type == TriggerType.SCHEDULE.value diff --git a/surfsense_backend/tests/unit/automations/test_persistence_enums.py b/surfsense_backend/tests/unit/automations/test_persistence_enums.py new file mode 100644 index 000000000..59703dfc6 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_persistence_enums.py @@ -0,0 +1,45 @@ +"""Lock the persistence enum string values + members. + +These enums are mirrored by Postgres enum types, embedded in stored DB +rows, and surfaced in the JSON API. Renaming a value (or removing a +member) silently breaks production data and previously-issued API +responses, so the strings + the set of members are the contract. +""" + +from __future__ import annotations + +import pytest + +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.enums.trigger_type import TriggerType + +pytestmark = pytest.mark.unit + + +def test_automation_status_string_values_are_stable() -> None: + """The exact strings persisted to Postgres and served in API JSON.""" + assert {member.value for member in AutomationStatus} == { + "active", + "paused", + "archived", + } + + +def test_run_status_string_values_are_stable() -> None: + """Run lifecycle states embedded in the ``automation_runs`` table.""" + assert {member.value for member in RunStatus} == { + "pending", + "running", + "succeeded", + "failed", + "cancelled", + "timed_out", + } + + +def test_trigger_type_keeps_manual_member_even_though_unregistered() -> None: + """``MANUAL`` is reserved (mirrors the Postgres enum) but the trigger + store does not register it in v1. The enum must keep both members so + existing DB rows and the schema migration plan stay valid.""" + assert {member.value for member in TriggerType} == {"schedule", "manual"} diff --git a/surfsense_backend/tests/unit/automations/test_stores.py b/surfsense_backend/tests/unit/automations/test_stores.py new file mode 100644 index 000000000..e54062d64 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_stores.py @@ -0,0 +1,115 @@ +"""Lock the trigger + action registry contracts. + +Both stores share the same API shape (register/get/all + duplicate-raise), +so they're tested together to keep the contract visible side-by-side. +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from app.automations.actions.store import ( + get_action, + register_action, +) +from app.automations.actions.types import ActionDefinition +from app.automations.triggers.store import ( + all_triggers, + get_trigger, + register_trigger, +) +from app.automations.triggers.types import TriggerDefinition + +pytestmark = pytest.mark.unit + + +class _Params(BaseModel): + """Empty params model used by test-only registrations.""" + + +def _trigger(type_: str = "test_trigger") -> TriggerDefinition: + return TriggerDefinition(type=type_, description="Test trigger.", params_model=_Params) + + +def _action(type_: str = "test_action") -> ActionDefinition: + return ActionDefinition( + type=type_, + name="Test", + description="Test action.", + params_model=_Params, + build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + ) + + +def test_register_trigger_then_get_trigger_returns_the_same_definition( + isolated_trigger_registry: None, +) -> None: + """The canonical round-trip: register, look up by type, get the same + definition back. Locks the basic registry contract.""" + definition = _trigger() + register_trigger(definition) + + assert get_trigger("test_trigger") is definition + + +def test_register_action_then_get_action_returns_the_same_definition( + isolated_action_registry: None, +) -> None: + """Same round-trip contract for the action registry.""" + definition = _action() + register_action(definition) + + assert get_action("test_action") is definition + + +def test_get_trigger_returns_none_for_unknown_type( + isolated_trigger_registry: None, +) -> None: + """An unknown type returns ``None`` (not raises). Lets callers like + the dispatcher branch on "is this trigger still registered?" without + try/except.""" + assert get_trigger("never_registered") is None + + +def test_get_action_returns_none_for_unknown_type( + isolated_action_registry: None, +) -> None: + """Same ``None``-not-raise contract on the action side.""" + assert get_action("never_registered") is None + + +def test_register_trigger_rejects_duplicate_type( + isolated_trigger_registry: None, +) -> None: + """Re-registering the same ``type`` raises rather than silently + overwriting. Locks the safety net against accidental double-import + (e.g., circular imports re-running the registration block).""" + register_trigger(_trigger()) + + with pytest.raises(ValueError, match="test_trigger"): + register_trigger(_trigger()) + + +def test_register_action_rejects_duplicate_type( + isolated_action_registry: None, +) -> None: + """Same duplicate-rejection contract on the action side.""" + register_action(_action()) + + with pytest.raises(ValueError, match="test_action"): + register_action(_action()) + + +def test_all_triggers_returns_defensive_snapshot( + isolated_trigger_registry: None, +) -> None: + """``all_triggers()`` returns a copy: mutating the returned dict does + not corrupt the internal registry. Locks the snapshot contract that + UI/listing endpoints rely on.""" + register_trigger(_trigger("snapshot_test")) + + snapshot = all_triggers() + snapshot.pop("snapshot_test") + + assert get_trigger("snapshot_test") is not None \ No newline at end of file From 958bf9f95ad0bf32450b0aa86bc99218c683aad3 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 21:10:24 +0200 Subject: [PATCH 088/133] fix(automations/agent_task): use in-memory checkpointer to avoid Celery PoolTimeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared AsyncPostgresSaver caches DB connections in a module-level pool. Cached connections are bound to the asyncio loop that opened them, but `run_async_celery_task` discards the loop on each task's exit — so after the first task the pool holds connections pointing to a dead loop, and the next automation hangs 30s before failing with `PoolTimeout: couldn't get a connection after 30.00 sec`. Swap agent_task to `InMemorySaver`; automation runs only need state within one Celery task, so nothing is lost. Site-local TODO tracks the proper future fix (dispose the checkpointer pool around each Celery task, mirroring `_dispose_shared_db_engine`). --- .../actions/agent_task/dependencies.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/surfsense_backend/app/automations/actions/agent_task/dependencies.py b/surfsense_backend/app/automations/actions/agent_task/dependencies.py index 12273aa0f..79107cd65 100644 --- a/surfsense_backend/app/automations/actions/agent_task/dependencies.py +++ b/surfsense_backend/app/automations/actions/agent_task/dependencies.py @@ -5,17 +5,17 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from langgraph.checkpoint.memory import InMemorySaver from sqlalchemy.ext.asyncio import AsyncSession from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( - get_chat_checkpointer, setup_connector_and_firecrawl, ) class DependencyError(Exception): - """An external dependency (LLM config, checkpointer, ...) refused to load.""" + """An external dependency (LLM config, connector service, ...) refused to load.""" @dataclass(frozen=True, slots=True) @@ -34,7 +34,7 @@ async def build_dependencies( session: AsyncSession, search_space_id: int, ) -> AgentDependencies: - """Load the LLM bundle, connector service, and checkpointer for one invoke. + """Load the LLM bundle, connector service, and a per-invoke in-memory checkpointer. Uses the search space's default LLM config (``config_id=-1``). Per-step model overrides land in a future iteration alongside the ``model`` param. @@ -48,7 +48,24 @@ async def build_dependencies( connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( session, search_space_id=search_space_id ) - checkpointer = await get_chat_checkpointer() + # Quick fix: use an in-memory checkpointer for automation runs. + # + # The shared Postgres checkpointer caches DB connections in a + # module-level pool. Each cached connection is bound to the asyncio + # loop that opened it. Celery throws away the loop after every task, + # so the pool ends up full of connections pointing to a dead loop, + # and the next Celery task (running on a fresh loop) can't use any + # of them — it hangs 30s and fails with + # `PoolTimeout: couldn't get a connection after 30.00 sec`. + # + # InMemorySaver has no cached connections, no loop binding — each + # Celery task creates one and drops it on exit. + # + # TODO(checkpointer): proper fix is to dispose the checkpointer + # pool around each Celery task in `run_async_celery_task`, the same + # way `_dispose_shared_db_engine` already does for the SQLAlchemy + # pool. Then this site can switch back to the shared checkpointer. + checkpointer = InMemorySaver() return AgentDependencies( llm=llm, agent_config=agent_config, From 94e834134f7269260095b1c7d1763bcf4fbd582b Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 19:21:29 -0700 Subject: [PATCH 089/133] chore: linting --- .../versions/144_add_automation_tables.py | 4 +- .../main_agent/tools/automation/prompt.py | 1 - .../task_tool.py | 4 +- .../builtins/deliverables/tools/podcast.py | 4 +- .../deliverables/tools/video_presentation.py | 7 +- .../app/agents/new_chat/tools/podcast.py | 8 +- .../new_chat/tools/video_presentation.py | 4 +- .../app/automations/actions/__init__.py | 2 +- .../actions/agent_task/__init__.py | 2 +- .../automations/actions/agent_task/invoke.py | 1 - .../app/automations/persistence/models/run.py | 2 +- .../app/automations/runtime/executor.py | 11 +- .../app/automations/runtime/step.py | 21 ++- .../schemas/definition/execution.py | 4 +- .../schemas/definition/plan_step.py | 4 +- .../schemas/definition/trigger_spec.py | 4 +- .../app/automations/services/automation.py | 42 +++-- .../app/automations/services/run.py | 12 +- .../app/automations/services/trigger.py | 14 +- .../app/automations/tasks/execute_run.py | 2 +- .../app/automations/tasks/schedule_tick.py | 4 +- .../app/automations/triggers/__init__.py | 2 +- .../automations/triggers/schedule/__init__.py | 2 +- .../app/automations/triggers/schedule/cron.py | 6 +- .../automations/triggers/schedule/params.py | 4 +- surfsense_backend/app/db.py | 1 - surfsense_backend/app/routes/__init__.py | 3 +- .../streaming/context/deepagents_todos.py | 4 +- .../chat/streaming/flows/new_chat/auto_pin.py | 8 +- .../streaming/flows/new_chat/input_state.py | 4 +- .../flows/new_chat/llm_capability.py | 4 +- .../streaming/flows/new_chat/orchestrator.py | 27 ++-- .../flows/new_chat/runtime_context.py | 4 +- .../streaming/flows/new_chat/title_gen.py | 8 +- .../flows/resume_chat/orchestrator.py | 37 ++--- .../flows/resume_chat/resume_routing.py | 4 +- .../flows/shared/assistant_finalize.py | 4 +- .../streaming/flows/shared/finally_cleanup.py | 4 +- .../streaming/flows/shared/premium_quota.py | 10 +- .../tasks/chat/streaming/flows/shared/span.py | 3 +- .../streaming/flows/shared/terminal_error.py | 3 +- .../actions/agent_task/test_finalize.py | 6 +- .../unit/automations/runtime/test_retries.py | 4 +- .../schemas/definition/test_envelope.py | 4 +- .../templating/test_environment.py | 4 +- .../unit/automations/test_definition_types.py | 2 +- .../tests/unit/automations/test_stores.py | 8 +- .../triggers/schedule/test_cron.py | 8 +- .../test_parallel_refactor_parity.py | 20 ++- surfsense_web/app/(home)/free/page.tsx | 10 +- surfsense_web/app/(home)/privacy/page.tsx | 98 ++++++------ surfsense_web/app/api/zero/query/route.ts | 2 +- .../edit/automation-edit-content.tsx | 5 +- .../edit/components/automation-edit-form.tsx | 5 +- .../new-chat/[[...chat_id]]/page.tsx | 11 +- .../[search_space_id]/team/team-content.tsx | 2 +- .../atoms/members/members-query.atoms.ts | 8 +- .../assistant-ui/assistant-message.tsx | 2 +- .../components/obsidian-connect-form.tsx | 3 +- .../components/circleback-config.tsx | 2 +- .../views/connector-edit-view.tsx | 1 + .../views/connector-accounts-list-view.tsx | 1 + .../assistant-ui/inline-mention-editor.tsx | 20 ++- .../components/assistant-ui/thread.tsx | 89 ++++++----- .../components/assistant-ui/user-message.tsx | 8 +- .../components/editor-panel/editor-panel.tsx | 1 + .../components/free-chat/anonymous-chat.tsx | 26 ++-- .../layout/ui/icon-rail/SearchSpaceAvatar.tsx | 6 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 2 +- .../layout/ui/tabs/DocumentTabContent.tsx | 1 + .../new-chat/composer-suggestion-popup.tsx | 5 +- .../new-chat/document-mention-picker.tsx | 144 ++++++++++-------- .../components/new-chat/prompt-picker.tsx | 4 +- .../settings/general-settings-manager.tsx | 2 +- .../settings/prompt-config-manager.tsx | 8 +- .../components/tool-ui/generate-podcast.tsx | 9 +- .../generate-video-presentation.tsx | 2 +- .../hooks/use-search-source-connectors.ts | 12 +- surfsense_web/lib/auth-utils.ts | 1 + surfsense_web/lib/posthog/events.ts | 2 +- 80 files changed, 443 insertions(+), 404 deletions(-) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 8d836095d..39f927417 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -98,9 +98,7 @@ def upgrade() -> None: op.execute( "CREATE INDEX ix_automation_triggers_automation_id ON automation_triggers(automation_id);" ) - op.execute( - "CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);" - ) + op.execute("CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);") op.execute( "CREATE INDEX ix_automation_triggers_enabled ON automation_triggers(enabled);" ) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py index 45870e768..09854aa2e 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py @@ -28,7 +28,6 @@ from __future__ import annotations from datetime import UTC, datetime - _HEADER = """\ You are the SurfSense automation drafter. Convert the user intent below into a SINGLE JSON object matching the AutomationCreate schema. Output diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py index 91a0be506..eaed9a55f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py @@ -404,9 +404,7 @@ def build_task_tool_with_parent_config( continue messages = payload.get("messages") or [] last_text = _safe_message_text(messages[-1]).rstrip() if messages else "" - message_blocks.append( - f"[task {task_index}] {last_text or '<empty>'}" - ) + message_blocks.append(f"[task {task_index}] {last_text or '<empty>'}") try: child_trace = _build_tool_trace(messages) except Exception: diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py index 84617d38b..298257799 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py @@ -117,9 +117,7 @@ def create_generate_podcast_tool( "podcast_id": podcast_id, "title": podcast_title, "file_location": file_location, - "message": ( - "Podcast generated and saved to your podcast panel." - ), + "message": ("Podcast generated and saved to your podcast panel."), } return with_receipt( payload=payload, diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py index 8c52293de..5407c8834 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py @@ -126,8 +126,7 @@ def create_generate_video_presentation_tool( elapsed, ) err = ( - "Background worker reported FAILED status for this " - "video presentation." + "Background worker reported FAILED status for this video presentation." ) payload = { "status": VideoPresentationStatus.FAILED.value, @@ -151,9 +150,7 @@ def create_generate_video_presentation_tool( except Exception as e: error_message = str(e) - logger.exception( - "[generate_video_presentation] Error: %s", error_message - ) + logger.exception("[generate_video_presentation] Error: %s", error_message) payload = { "status": VideoPresentationStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/agents/new_chat/tools/podcast.py b/surfsense_backend/app/agents/new_chat/tools/podcast.py index 36aecfe49..83ac98768 100644 --- a/surfsense_backend/app/agents/new_chat/tools/podcast.py +++ b/surfsense_backend/app/agents/new_chat/tools/podcast.py @@ -131,9 +131,7 @@ def create_generate_podcast_tool( "podcast_id": podcast_id, "title": podcast_title, "file_location": file_location, - "message": ( - "Podcast generated and saved to your podcast panel." - ), + "message": ("Podcast generated and saved to your podcast panel."), } # Only other terminal state is FAILED. @@ -146,9 +144,7 @@ def create_generate_podcast_tool( "status": PodcastStatus.FAILED.value, "podcast_id": podcast_id, "title": podcast_title, - "error": ( - "Background worker reported FAILED status for this podcast." - ), + "error": ("Background worker reported FAILED status for this podcast."), } except Exception as e: diff --git a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py index 4bf13b28e..34f5183ca 100644 --- a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py +++ b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py @@ -127,9 +127,7 @@ def create_generate_video_presentation_tool( except Exception as e: error_message = str(e) - logger.exception( - "[generate_video_presentation] Error: %s", error_message - ) + logger.exception("[generate_video_presentation] Error: %s", error_message) return { "status": VideoPresentationStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/automations/actions/__init__.py b/surfsense_backend/app/automations/actions/__init__.py index 9ef091cb3..72669532f 100644 --- a/surfsense_backend/app/automations/actions/__init__.py +++ b/surfsense_backend/app/automations/actions/__init__.py @@ -21,4 +21,4 @@ __all__ = [ ] # Built-in actions self-register at import time. -from . import agent_task # noqa: E402, F401 +from . import agent_task # noqa: F401 diff --git a/surfsense_backend/app/automations/actions/agent_task/__init__.py b/surfsense_backend/app/automations/actions/agent_task/__init__.py index 308812211..3a42a2815 100644 --- a/surfsense_backend/app/automations/actions/agent_task/__init__.py +++ b/surfsense_backend/app/automations/actions/agent_task/__init__.py @@ -12,4 +12,4 @@ from .params import AgentTaskActionParams __all__ = ["AgentTaskActionParams", "build_handler"] # Side-effect: register on the actions store. -from . import definition # noqa: E402, F401 +from . import definition # noqa: F401 diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index a37e9beed..6cc92b232 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -13,7 +13,6 @@ from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent from app.db import ChatVisibility, async_session_maker from ..types import ActionContext - from .auto_decide import build_auto_decisions from .dependencies import build_dependencies from .finalize import extract_final_assistant_message diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 262e4c2bf..471b2df77 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -50,7 +50,7 @@ class AutomationRun(BaseModel, TimestampMixin): definition_snapshot = Column(JSONB, nullable=False) # merged & validated inputs the run was dispatched with - # (trigger.static_inputs ∪ producer runtime data, static wins on collision) + # (trigger.static_inputs union producer runtime data, static wins on collision) inputs = Column(JSONB, nullable=False, server_default="{}") # one entry per executed step; agent_task entries carry their own # `agent_session_id` inside their entry diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index b8a377e5b..6a33ab314 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -6,9 +6,9 @@ from typing import Any from sqlalchemy.ext.asyncio import AsyncSession +from app.automations.actions.types import ActionContext from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun -from app.automations.actions.types import ActionContext from app.automations.schemas.definition.envelope import AutomationDefinition from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context @@ -32,7 +32,10 @@ async def execute_run(session: AsyncSession, run_id: int) -> None: await repository.mark_failed( session, run, - {"message": f"definition_snapshot invalid: {exc}", "type": type(exc).__name__}, + { + "message": f"definition_snapshot invalid: {exc}", + "type": type(exc).__name__, + }, ) await session.commit() return @@ -92,7 +95,9 @@ async def _run_on_failure( await session.commit() -def _build_template_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: +def _build_template_ctx( + run: AutomationRun, step_outputs: dict[str, Any] +) -> dict[str, Any]: automation = run.automation trigger = run.trigger return build_run_context( diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py index ac18b5e1f..6e7c9c671 100644 --- a/surfsense_backend/app/automations/runtime/step.py +++ b/surfsense_backend/app/automations/runtime/step.py @@ -30,14 +30,18 @@ async def execute_step( try: should_run = evaluate_predicate(step.when, template_context) except Exception as exc: - return _result(step, "failed", started_at, attempts=0, error=_error(exc, "when")) + return _result( + step, "failed", started_at, attempts=0, error=_error(exc, "when") + ) if not should_run: return _result(step, "skipped", started_at, attempts=0) try: resolved_params = render_value(step.params, template_context) except Exception as exc: - return _result(step, "failed", started_at, attempts=0, error=_error(exc, "render")) + return _result( + step, "failed", started_at, attempts=0, error=_error(exc, "render") + ) action = get_action(step.action) if action is None: @@ -46,12 +50,17 @@ async def execute_step( "failed", started_at, attempts=0, - error={"message": f"action not registered: {step.action}", "type": "ActionNotFound"}, + error={ + "message": f"action not registered: {step.action}", + "type": "ActionNotFound", + }, ) handler = action.build_handler(action_context) - max_retries = step.max_retries if step.max_retries is not None else default_max_retries + max_retries = ( + step.max_retries if step.max_retries is not None else default_max_retries + ) timeout = step.timeout_seconds or default_timeout_seconds try: @@ -62,7 +71,9 @@ async def execute_step( timeout=timeout, ) except Exception as exc: - return _result(step, "failed", started_at, attempts=max_retries + 1, error=_error(exc)) + return _result( + step, "failed", started_at, attempts=max_retries + 1, error=_error(exc) + ) return _result(step, "succeeded", started_at, attempts=attempts, result=result) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index 61861f8d8..bdbad62f8 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -12,7 +12,9 @@ from .plan_step import PlanStep class Execution(BaseModel): model_config = ConfigDict(extra="forbid") - timeout_seconds: int = Field(default=600, gt=0, description="Wall-clock cap for the run.") + timeout_seconds: int = Field( + default=600, gt=0, description="Wall-clock cap for the run." + ) max_retries: int = Field(default=2, ge=0, description="Per-step retry budget.") retry_backoff: Literal["exponential", "linear", "none"] = "exponential" concurrency: Literal["drop_if_running", "queue", "always"] = "drop_if_running" diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py index 5d16f1f3e..0d3bb9dfc 100644 --- a/surfsense_backend/app/automations/schemas/definition/plan_step.py +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -11,7 +11,9 @@ class PlanStep(BaseModel): model_config = ConfigDict(extra="forbid") step_id: str = Field(..., min_length=1, description="Unique within the plan.") - action: str = Field(..., min_length=1, description="Action type; resolved via registry.") + action: str = Field( + ..., min_length=1, description="Action type; resolved via registry." + ) when: str | None = Field( default=None, description="Optional predicate; step is skipped when falsy.", diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py index a359a2f63..e6a995bbf 100644 --- a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -10,7 +10,9 @@ from pydantic import BaseModel, ConfigDict, Field class TriggerSpec(BaseModel): model_config = ConfigDict(extra="forbid") - type: str = Field(..., min_length=1, description="Trigger type; resolved via registry.") + type: str = Field( + ..., min_length=1, description="Trigger type; resolved via registry." + ) params: dict[str, Any] = Field( default_factory=dict, description="Type-specific params; validated against the trigger's schema.", diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py index 9140da3b5..0d2937e0e 100644 --- a/surfsense_backend/app/automations/services/automation.py +++ b/surfsense_backend/app/automations/services/automation.py @@ -10,14 +10,14 @@ from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.trigger import AutomationTrigger from app.automations.schemas.api import ( AutomationCreate, AutomationUpdate, TriggerCreate, ) -from app.automations.persistence.enums.trigger_type import TriggerType -from app.automations.persistence.models.automation import Automation -from app.automations.persistence.models.trigger import AutomationTrigger from app.automations.triggers import get_trigger from app.automations.triggers.schedule import compute_next_fire_at from app.db import Permission, User, get_async_session @@ -34,7 +34,9 @@ class AutomationService: async def create(self, payload: AutomationCreate) -> Automation: """Create an automation and its initial triggers in one transaction.""" - await self._authorize(payload.search_space_id, Permission.AUTOMATIONS_CREATE.value) + await self._authorize( + payload.search_space_id, Permission.AUTOMATIONS_CREATE.value + ) automation = Automation( search_space_id=payload.search_space_id, @@ -67,22 +69,32 @@ class AutomationService: ) rows = ( - await self.session.execute( - base.order_by(Automation.created_at.desc()).limit(limit).offset(offset) + ( + await self.session.execute( + base.order_by(Automation.created_at.desc()) + .limit(limit) + .offset(offset) + ) ) - ).scalars().all() + .scalars() + .all() + ) return list(rows), int(total or 0) async def get(self, automation_id: int) -> Automation: """Get an automation with its triggers loaded.""" automation = await self._get_with_triggers_or_raise(automation_id) - await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_READ.value) + await self._authorize( + automation.search_space_id, Permission.AUTOMATIONS_READ.value + ) return automation async def update(self, automation_id: int, patch: AutomationUpdate) -> Automation: """Patch fields. Bumps ``version`` when ``definition`` changes.""" automation = await self._get_with_triggers_or_raise(automation_id) - await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_UPDATE.value) + await self._authorize( + automation.search_space_id, Permission.AUTOMATIONS_UPDATE.value + ) data = patch.model_dump(exclude_unset=True) @@ -93,7 +105,9 @@ class AutomationService: if "status" in data: automation.status = data["status"] if "definition" in data: - automation.definition = patch.definition.model_dump(mode="json", by_alias=True) + automation.definition = patch.definition.model_dump( + mode="json", by_alias=True + ) automation.version += 1 await self.session.commit() @@ -102,7 +116,9 @@ class AutomationService: async def delete(self, automation_id: int) -> None: """Delete an automation; FK cascades remove triggers and runs.""" automation = await self._get_or_raise(automation_id) - await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_DELETE.value) + await self._authorize( + automation.search_space_id, Permission.AUTOMATIONS_DELETE.value + ) await self.session.delete(automation) await self.session.commit() @@ -141,7 +157,9 @@ def _build_trigger(spec: TriggerCreate) -> AutomationTrigger: """Validate trigger params via its registered Pydantic model and build the ORM row.""" definition = get_trigger(spec.type.value) if definition is None: - raise HTTPException(status_code=422, detail=f"unknown trigger type {spec.type.value!r}") + raise HTTPException( + status_code=422, detail=f"unknown trigger type {spec.type.value!r}" + ) try: validated = definition.params_model.model_validate(spec.params) diff --git a/surfsense_backend/app/automations/services/run.py b/surfsense_backend/app/automations/services/run.py index ac9970241..3ef80416f 100644 --- a/surfsense_backend/app/automations/services/run.py +++ b/surfsense_backend/app/automations/services/run.py @@ -36,10 +36,16 @@ class RunService: ) rows = ( - await self.session.execute( - base.order_by(AutomationRun.created_at.desc()).limit(limit).offset(offset) + ( + await self.session.execute( + base.order_by(AutomationRun.created_at.desc()) + .limit(limit) + .offset(offset) + ) ) - ).scalars().all() + .scalars() + .all() + ) return list(rows), int(total or 0) async def get(self, *, automation_id: int, run_id: int) -> AutomationRun: diff --git a/surfsense_backend/app/automations/services/trigger.py b/surfsense_backend/app/automations/services/trigger.py index c76cc0740..29ac84557 100644 --- a/surfsense_backend/app/automations/services/trigger.py +++ b/surfsense_backend/app/automations/services/trigger.py @@ -8,10 +8,10 @@ from fastapi import Depends, HTTPException from pydantic import ValidationError from sqlalchemy.ext.asyncio import AsyncSession -from app.automations.schemas.api import TriggerCreate, TriggerUpdate from app.automations.persistence.enums.trigger_type import TriggerType from app.automations.persistence.models.automation import Automation from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.schemas.api import TriggerCreate, TriggerUpdate from app.automations.triggers import get_trigger from app.automations.triggers.schedule import compute_next_fire_at from app.db import Permission, User, get_async_session @@ -40,7 +40,9 @@ class TriggerService: params=validated_params, static_inputs=payload.static_inputs, enabled=payload.enabled, - next_fire_at=_initial_next_fire(payload.type, validated_params, payload.enabled), + next_fire_at=_initial_next_fire( + payload.type, validated_params, payload.enabled + ), ) self.session.add(trigger) await self.session.commit() @@ -54,7 +56,9 @@ class TriggerService: trigger_id: int, patch: TriggerUpdate, ) -> AutomationTrigger: - await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + await self._authorize_automation( + automation_id, Permission.AUTOMATIONS_UPDATE.value + ) trigger = await self._get_trigger_or_raise(automation_id, trigger_id) data = patch.model_dump(exclude_unset=True) @@ -80,7 +84,9 @@ class TriggerService: return trigger async def remove(self, *, automation_id: int, trigger_id: int) -> None: - await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + await self._authorize_automation( + automation_id, Permission.AUTOMATIONS_UPDATE.value + ) trigger = await self._get_trigger_or_raise(automation_id, trigger_id) await self.session.delete(trigger) await self.session.commit() diff --git a/surfsense_backend/app/automations/tasks/execute_run.py b/surfsense_backend/app/automations/tasks/execute_run.py index 5fc84698b..ed448515d 100644 --- a/surfsense_backend/app/automations/tasks/execute_run.py +++ b/surfsense_backend/app/automations/tasks/execute_run.py @@ -17,7 +17,7 @@ TASK_NAME = "automation_run_execute" @celery_app.task(name=TASK_NAME, bind=True) -def automation_run_execute(self, run_id: int) -> None: # noqa: ARG001 — Celery bind +def automation_run_execute(self, run_id: int) -> None: """Execute one ``AutomationRun``. Idempotent: terminal runs no-op.""" return run_async_celery_task(lambda: _impl(run_id)) diff --git a/surfsense_backend/app/automations/tasks/schedule_tick.py b/surfsense_backend/app/automations/tasks/schedule_tick.py index 385bd7242..90fff66fc 100644 --- a/surfsense_backend/app/automations/tasks/schedule_tick.py +++ b/surfsense_backend/app/automations/tasks/schedule_tick.py @@ -103,9 +103,7 @@ async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> await session.commit() -async def _claim_due_triggers( - session: AsyncSession, *, now: datetime -) -> list[_Claim]: +async def _claim_due_triggers(session: AsyncSession, *, now: datetime) -> list[_Claim]: """Lock and advance due rows; return per-trigger fire context.""" stmt = ( select(AutomationTrigger) diff --git a/surfsense_backend/app/automations/triggers/__init__.py b/surfsense_backend/app/automations/triggers/__init__.py index d7abb6b5d..f630ebf6f 100644 --- a/surfsense_backend/app/automations/triggers/__init__.py +++ b/surfsense_backend/app/automations/triggers/__init__.py @@ -17,4 +17,4 @@ __all__ = [ ] # Built-in triggers self-register at import time. -from . import schedule # noqa: E402, F401 +from . import schedule # noqa: F401 diff --git a/surfsense_backend/app/automations/triggers/schedule/__init__.py b/surfsense_backend/app/automations/triggers/schedule/__init__.py index 5587692b9..92f478aac 100644 --- a/surfsense_backend/app/automations/triggers/schedule/__init__.py +++ b/surfsense_backend/app/automations/triggers/schedule/__init__.py @@ -15,4 +15,4 @@ __all__ = [ ] # Side-effect: register on the triggers store. -from . import definition # noqa: E402, F401 +from . import definition # noqa: F401 diff --git a/surfsense_backend/app/automations/triggers/schedule/cron.py b/surfsense_backend/app/automations/triggers/schedule/cron.py index 7155bab33..a8401e4a3 100644 --- a/surfsense_backend/app/automations/triggers/schedule/cron.py +++ b/surfsense_backend/app/automations/triggers/schedule/cron.py @@ -32,6 +32,10 @@ def compute_next_fire_at(cron: str, timezone: str, *, after: datetime) -> dateti given timezone before evaluation so DST and IANA rules apply correctly. """ tz = ZoneInfo(timezone) - base = after.astimezone(tz) if after.tzinfo else after.replace(tzinfo=UTC).astimezone(tz) + base = ( + after.astimezone(tz) + if after.tzinfo + else after.replace(tzinfo=UTC).astimezone(tz) + ) nxt: datetime = croniter(cron, base).get_next(datetime) return nxt.astimezone(UTC) diff --git a/surfsense_backend/app/automations/triggers/schedule/params.py b/surfsense_backend/app/automations/triggers/schedule/params.py index 21da84f68..f3945a1b8 100644 --- a/surfsense_backend/app/automations/triggers/schedule/params.py +++ b/surfsense_backend/app/automations/triggers/schedule/params.py @@ -10,7 +10,9 @@ from .cron import InvalidCronError, validate_cron class ScheduleTriggerParams(BaseModel): model_config = ConfigDict(extra="forbid") - cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) + cron: str = Field( + ..., description="Five-field cron expression.", examples=["0 9 * * 1-5"] + ) timezone: str = Field(..., description="IANA timezone.", examples=["Africa/Kigali"]) @model_validator(mode="after") diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index ac880ded5..fe2e53268 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -2605,7 +2605,6 @@ from app.automations.persistence import ( # noqa: E402, F401 AutomationTrigger, ) - engine = create_async_engine( DATABASE_URL, pool_size=30, diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index ef1c9312a..48a095456 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -1,5 +1,7 @@ from fastapi import APIRouter +from app.automations.api import router as automations_router + from .agent_action_log_route import router as agent_action_log_router from .agent_flags_route import router as agent_flags_router from .agent_permissions_route import router as agent_permissions_router @@ -7,7 +9,6 @@ from .agent_revert_route import router as agent_revert_router from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) -from app.automations.api import router as automations_router from .chat_comments_routes import router as chat_comments_router from .circleback_webhook_route import router as circleback_webhook_router from .clickup_add_connector_route import router as clickup_add_connector_router diff --git a/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py index 0bbf4f0a5..b9cbf6506 100644 --- a/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py +++ b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py @@ -19,9 +19,7 @@ def extract_todos_from_deepagents(command_output: Any) -> dict: elif isinstance(command_output, dict): if "todos" in command_output: todos_data = command_output.get("todos", []) - elif "update" in command_output and isinstance( - command_output["update"], dict - ): + elif "update" in command_output and isinstance(command_output["update"], dict): todos_data = command_output["update"].get("todos", []) return {"todos": todos_data} diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py index cb20eb011..af496cee7 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py @@ -69,17 +69,13 @@ async def resolve_initial_auto_pin( "pin.requires_image_input": requires_image_input, }, ) - return AutoPinResult( - llm_config_id=pinned.resolved_llm_config_id, error=None - ) + return AutoPinResult(llm_config_id=pinned.resolved_llm_config_id, error=None) except ValueError as pin_error: # The "no vision-capable cfg" path raises a ValueError whose message # we map to the friendly image-input SSE error so the user sees the # same message regardless of whether the gate fired in the resolver or # in ``llm_capability.assert_vision_capability_for_image_turn``. - is_vision_failure = ( - requires_image_input and "vision-capable" in str(pin_error) - ) + is_vision_failure = requires_image_input and "vision-capable" in str(pin_error) error_code = ( "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" if is_vision_failure diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py index fb171c244..f508571b0 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py @@ -207,9 +207,7 @@ async def _resolve_mentions_for_query( try: chip_objs.append(MentionedDocumentInfo.model_validate(raw)) except Exception: - logger.debug( - "stream_new_chat: dropping malformed mention chip %r", raw - ) + logger.debug("stream_new_chat: dropping malformed mention chip %r", raw) resolved = await resolve_mentions( session, diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py index ff5a56eec..9f4e5d2d8 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py @@ -48,9 +48,7 @@ def check_image_input_capability( return None model_label = agent_config.config_name or agent_config.model_name or "model" - ot.add_event( - "quota.denied", {"quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT"} - ) + ot.add_event("quota.denied", {"quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT"}) return ( ( f"The selected model ({model_label}) does not support " diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py index bca72b5ea..6d0853502 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py @@ -259,7 +259,8 @@ async def stream_new_chat( if needs_premium_quota(agent_config, user_id): premium_reservation = await reserve_premium( - agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + agent_config=agent_config, + user_id=user_id, # type: ignore[arg-type] ) if not premium_reservation.allowed: ot.add_event("quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"}) @@ -492,7 +493,9 @@ async def stream_new_chat( # --- Block 4: First SSE frames --- - for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + for sse in iter_initial_frames( + streaming_service, turn_id=stream_result.turn_id + ): yield sse # --- Block 5: Persistence join + message-id frames --- @@ -693,7 +696,9 @@ async def stream_new_chat( fallback_commit_search_space_id=search_space_id, fallback_commit_created_by_id=user_id, fallback_commit_filesystem_mode=( - filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + filesystem_selection.mode + if filesystem_selection + else FilesystemMode.CLOUD ), fallback_commit_thread_id=chat_id, runtime_context=runtime_context, @@ -715,11 +720,7 @@ async def stream_new_chat( title_emitted = True # Account for the case where the task completed but produced no # title — flip the flag anyway so we don't keep checking it. - if ( - title_task is not None - and title_task.done() - and not title_emitted - ): + if title_task is not None and title_task.done() and not title_emitted: title_emitted = True _perf_log.info( @@ -811,9 +812,7 @@ async def stream_new_chat( end_turn(str(chat_id)) if premium_reservation is not None and user_id: - await release_premium( - reservation=premium_reservation, user_id=user_id - ) + await release_premium(reservation=premium_reservation, user_id=user_id) await close_session_and_clear_ai_responding(session, chat_id) @@ -852,9 +851,9 @@ async def stream_new_chat( # Break circular refs held by the agent graph, tools, and LLM # wrappers so the GC can reclaim them in a single pass. - agent = llm = connector_service = None # noqa: F841 - input_state = stream_result = None # noqa: F841 - session = None # noqa: F841 + agent = llm = connector_service = None + input_state = stream_result = None + session = None run_gc_pass(log_prefix="stream_new_chat", chat_id=chat_id) close_chat_request_span( diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py index 1f11be1fe..cf1e8c3fb 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py @@ -30,9 +30,7 @@ def build_new_chat_runtime_context( return SurfSenseContextSchema( search_space_id=search_space_id, mentioned_document_ids=list(mentioned_document_ids or []), - mentioned_folder_ids=list( - accepted_folder_ids or mentioned_folder_ids or [] - ), + mentioned_folder_ids=list(accepted_folder_ids or mentioned_folder_ids or []), request_id=request_id, turn_id=turn_id, ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py index 11312110f..7db45941b 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py @@ -133,12 +133,8 @@ async def _generate_title( # inherited Azure endpoint — see ``provider_api_base`` for the # same bug repro on the image-gen / vision paths. raw_model = getattr(llm, "model", "") or "" - provider_prefix = ( - raw_model.split("/", 1)[0] if "/" in raw_model else None - ) - provider_value = ( - agent_config.provider if agent_config is not None else None - ) + provider_prefix = raw_model.split("/", 1)[0] if "/" in raw_model else None + provider_value = agent_config.provider if agent_config is not None else None title_api_base = resolve_api_base( provider=provider_value, provider_prefix=provider_prefix, diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py index b67ac987e..e1b95aa63 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py @@ -15,14 +15,10 @@ building blocks under ``flows/shared/``. Mirrors ``stream_new_chat`` but: from __future__ import annotations import contextlib -import gc import logging -import sys import time -import uuid as _uuid from collections.abc import AsyncGenerator from functools import partial -from typing import Any from uuid import UUID import anyio @@ -32,7 +28,7 @@ from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent from app.agents.new_chat.filesystem_selection import FilesystemMode, FilesystemSelection from app.agents.new_chat.middleware.busy_mutex import end_turn from app.config import config as _app_config -from app.db import ChatVisibility, async_session_maker, shielded_async_session +from app.db import ChatVisibility, async_session_maker from app.observability import otel as ot from app.services.chat_session_state_service import set_ai_responding from app.services.new_streaming_service import VercelStreamingService @@ -89,7 +85,7 @@ from app.tasks.chat.streaming.flows.shared.terminal_error import ( ) from app.tasks.chat.streaming.shared.stream_result import StreamResult from app.tasks.chat.streaming.shared.utils import resume_step_prefix -from app.utils.perf import get_perf_logger, log_system_snapshot +from app.utils.perf import get_perf_logger logger = logging.getLogger(__name__) _perf_log = get_perf_logger() @@ -217,12 +213,11 @@ async def stream_resume_chat( if needs_premium_quota(agent_config, user_id): premium_reservation = await reserve_premium( - agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + agent_config=agent_config, + user_id=user_id, # type: ignore[arg-type] ) if not premium_reservation.allowed: - ot.add_event( - "quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"} - ) + ot.add_event("quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"}) if requested_llm_config_id == 0: try: pinned_fb = await resolve_or_get_pinned_llm_config_id( @@ -396,7 +391,9 @@ async def stream_resume_chat( # --- First SSE frames --- - for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + for sse in iter_initial_frames( + streaming_service, turn_id=stream_result.turn_id + ): yield sse # --- Assistant-shell persistence + id frame --- @@ -517,7 +514,9 @@ async def stream_resume_chat( fallback_commit_search_space_id=search_space_id, fallback_commit_created_by_id=user_id, fallback_commit_filesystem_mode=( - filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + filesystem_selection.mode + if filesystem_selection + else FilesystemMode.CLOUD ), fallback_commit_thread_id=chat_id, runtime_context=runtime_context, @@ -589,9 +588,7 @@ async def stream_resume_chat( end_turn(str(chat_id)) if premium_reservation is not None and user_id: - await release_premium( - reservation=premium_reservation, user_id=user_id - ) + await release_premium(reservation=premium_reservation, user_id=user_id) await close_session_and_clear_ai_responding(session, chat_id) @@ -609,13 +606,11 @@ async def stream_resume_chat( if not busy_error_raised: with contextlib.suppress(Exception): end_turn(str(chat_id)) - _perf_log.info( - "[stream_resume] end_turn cleanup (chat_id=%s)", chat_id - ) + _perf_log.info("[stream_resume] end_turn cleanup (chat_id=%s)", chat_id) - agent = llm = connector_service = None # noqa: F841 - stream_result = None # noqa: F841 - session = None # noqa: F841 + agent = llm = connector_service = None + stream_result = None + session = None run_gc_pass(log_prefix="stream_resume", chat_id=chat_id) close_chat_request_span( diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py index 300fbc9bd..7f4f67aac 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py @@ -47,9 +47,7 @@ async def build_resume_routing( slice_decisions_by_tool_call, ) - parent_state = await agent.aget_state( - {"configurable": {"thread_id": str(chat_id)}} - ) + parent_state = await agent.aget_state({"configurable": {"thread_id": str(chat_id)}}) pending = collect_pending_tool_calls(parent_state) _perf_log.info( "[hitl_route] resume_entry chat_id=%s decisions=%d pending_subagents=%d", diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py index d16f81ac7..be1f102f3 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py @@ -49,9 +49,7 @@ async def finalize_assistant_message( was never assigned. """ if not ( - stream_result - and stream_result.turn_id - and stream_result.assistant_message_id + stream_result and stream_result.turn_id and stream_result.assistant_message_id ): return diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py index 8d425402f..f9454775e 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py @@ -39,9 +39,7 @@ async def close_session_and_clear_ai_responding( async with shielded_async_session() as fresh_session: await clear_ai_responding(fresh_session, chat_id) except Exception: - logger.warning( - "Failed to clear AI responding state for thread %s", chat_id - ) + logger.warning("Failed to clear AI responding state for thread %s", chat_id) with contextlib.suppress(Exception): session.expunge_all() diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py index 0ec40d275..cbf44764c 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py @@ -41,9 +41,7 @@ class PremiumReservation: allowed: bool -def needs_premium_quota( - agent_config: AgentConfig | None, user_id: str | None -) -> bool: +def needs_premium_quota(agent_config: AgentConfig | None, user_id: str | None) -> bool: return bool(agent_config is not None and user_id and agent_config.is_premium) @@ -61,8 +59,10 @@ async def reserve_premium( request_id = _uuid.uuid4().hex[:16] litellm_params = agent_config.litellm_params or {} base_model = ( - litellm_params.get("base_model") if isinstance(litellm_params, dict) else None - ) or agent_config.model_name or "" + (litellm_params.get("base_model") if isinstance(litellm_params, dict) else None) + or agent_config.model_name + or "" + ) reserve_amount_micros = estimate_call_reserve_micros( base_model=base_model, quota_reserve_tokens=agent_config.quota_reserve_tokens, diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py index 1e5169af1..74b9682ed 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py @@ -6,8 +6,7 @@ import contextlib import sys from typing import Any, Literal -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot def open_chat_request_span( diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py index c9db2caf2..b305dba23 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py @@ -15,8 +15,7 @@ from collections.abc import Iterator from typing import Any, Literal from app.agents.new_chat.errors import BusyError -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot from app.services.new_streaming_service import VercelStreamingService from app.tasks.chat.streaming.errors.classifier import classify_stream_exception from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py index bd49d764c..aa6c74549 100644 --- a/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py @@ -72,7 +72,11 @@ def test_extract_returns_none_when_no_assistant_text_is_present() -> None: anything?" rather than guess whether ``""`` means silence or empty output. Empty-string contents are normalized to ``None`` too.""" no_ai = {"messages": [HumanMessage(content="just a question")]} - only_tools = {"messages": [AIMessage(content=[{"type": "tool_use", "name": "x", "input": {}}])]} + only_tools = { + "messages": [ + AIMessage(content=[{"type": "tool_use", "name": "x", "input": {}}]) + ] + } empty_string = {"messages": [AIMessage(content=" ")]} assert extract_final_assistant_message(no_ai) is None diff --git a/surfsense_backend/tests/unit/automations/runtime/test_retries.py b/surfsense_backend/tests/unit/automations/runtime/test_retries.py index f0f12ca59..05fd02ab6 100644 --- a/surfsense_backend/tests/unit/automations/runtime/test_retries.py +++ b/surfsense_backend/tests/unit/automations/runtime/test_retries.py @@ -33,7 +33,9 @@ async def test_with_retries_returns_result_and_attempts_one_on_first_success() - assert calls == 1 -async def test_with_retries_returns_attempt_count_when_succeeding_after_failures() -> None: +async def test_with_retries_returns_attempt_count_when_succeeding_after_failures() -> ( + None +): """A coroutine that fails twice then succeeds returns ``attempts=3`` (the actual attempt that produced the result). Locks the contract that the caller can distinguish first-try success from a recovery.""" diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py index c625b0ec9..d7b392a1d 100644 --- a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py @@ -11,7 +11,9 @@ from app.automations.schemas.definition.plan_step import PlanStep pytestmark = pytest.mark.unit -def test_automation_definition_accepts_minimal_valid_input_with_sensible_defaults() -> None: +def test_automation_definition_accepts_minimal_valid_input_with_sensible_defaults() -> ( + None +): """A definition with just ``name`` + a one-step ``plan`` is valid and fills in the rest with safe defaults so users don't have to write out every section to get started.""" diff --git a/surfsense_backend/tests/unit/automations/templating/test_environment.py b/surfsense_backend/tests/unit/automations/templating/test_environment.py index ec1c0ee40..64850c9c5 100644 --- a/surfsense_backend/tests/unit/automations/templating/test_environment.py +++ b/surfsense_backend/tests/unit/automations/templating/test_environment.py @@ -32,7 +32,9 @@ def test_environment_finalizes_datetime_output_to_iso_string() -> None: when emitting ``inputs.fired_at`` and other datetime values.""" dt = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) - assert render_template("{{ moment }}", {"moment": dt}) == "2026-05-28T14:30:00+00:00" + assert ( + render_template("{{ moment }}", {"moment": dt}) == "2026-05-28T14:30:00+00:00" + ) def test_environment_finalizes_none_output_to_empty_string() -> None: diff --git a/surfsense_backend/tests/unit/automations/test_definition_types.py b/surfsense_backend/tests/unit/automations/test_definition_types.py index 231e4fa97..2320b61d3 100644 --- a/surfsense_backend/tests/unit/automations/test_definition_types.py +++ b/surfsense_backend/tests/unit/automations/test_definition_types.py @@ -31,7 +31,7 @@ def test_action_definition_params_schema_reflects_params_model() -> None: name="N", description="D", params_model=_Topic, - build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + build_handler=lambda _ctx: lambda _p: {}, # type: ignore[arg-type,return-value] ) schema = definition.params_schema diff --git a/surfsense_backend/tests/unit/automations/test_stores.py b/surfsense_backend/tests/unit/automations/test_stores.py index e54062d64..d005d7be7 100644 --- a/surfsense_backend/tests/unit/automations/test_stores.py +++ b/surfsense_backend/tests/unit/automations/test_stores.py @@ -29,7 +29,9 @@ class _Params(BaseModel): def _trigger(type_: str = "test_trigger") -> TriggerDefinition: - return TriggerDefinition(type=type_, description="Test trigger.", params_model=_Params) + return TriggerDefinition( + type=type_, description="Test trigger.", params_model=_Params + ) def _action(type_: str = "test_action") -> ActionDefinition: @@ -38,7 +40,7 @@ def _action(type_: str = "test_action") -> ActionDefinition: name="Test", description="Test action.", params_model=_Params, - build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + build_handler=lambda _ctx: lambda _p: {}, # type: ignore[arg-type,return-value] ) @@ -112,4 +114,4 @@ def test_all_triggers_returns_defensive_snapshot( snapshot = all_triggers() snapshot.pop("snapshot_test") - assert get_trigger("snapshot_test") is not None \ No newline at end of file + assert get_trigger("snapshot_test") is not None diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py index 261e51b18..5c7580823 100644 --- a/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py +++ b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py @@ -45,8 +45,12 @@ def test_compute_next_fire_at_respects_dst_offset_change() -> None: winter_after = datetime(2026, 2, 15, 0, 0, tzinfo=UTC) summer_after = datetime(2026, 4, 15, 0, 0, tzinfo=UTC) - winter_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=winter_after) - summer_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=summer_after) + winter_fire = compute_next_fire_at( + "0 9 * * *", "America/New_York", after=winter_after + ) + summer_fire = compute_next_fire_at( + "0 9 * * *", "America/New_York", after=summer_after + ) assert winter_fire == datetime(2026, 2, 15, 14, 0, tzinfo=UTC) assert summer_fire == datetime(2026, 4, 15, 13, 0, tzinfo=UTC) diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py index eb24b4df8..ff4ca30df 100644 --- a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py @@ -33,7 +33,6 @@ import pytest from app.agents.new_chat.context import SurfSenseContextSchema from app.services.new_streaming_service import VercelStreamingService - from app.tasks.chat.stream_new_chat import ( stream_new_chat as old_stream_new_chat, stream_resume_chat as old_stream_resume_chat, @@ -152,7 +151,13 @@ class _FakeSurfsenseDoc: "user_query, image_urls, docs, expected_title, expected_action", [ ("hello world", None, [], "Understanding your request", "Processing"), - ("", ["data:image/png;base64,AAA"], [], "Understanding your request", "Processing"), + ( + "", + ["data:image/png;base64,AAA"], + [], + "Understanding your request", + "Processing", + ), ("", None, [], "Understanding your request", "Processing"), ( "doc question", @@ -209,9 +214,10 @@ def test_initial_thinking_step_collapses_many_doc_names() -> None: def test_image_capability_passes_without_images() -> None: - assert check_image_input_capability( - user_image_data_urls=None, agent_config=None - ) is None + assert ( + check_image_input_capability(user_image_data_urls=None, agent_config=None) + is None + ) def test_image_capability_passes_when_capability_unknown() -> None: @@ -500,9 +506,7 @@ def test_can_recover_provider_rate_limit_rejects_non_rate_limit_exception() -> N def test_spawn_set_ai_responding_bg_noop_without_user_id() -> None: async def _run() -> set[asyncio.Task]: background: set[asyncio.Task] = set() - spawn_set_ai_responding_bg( - chat_id=1, user_id=None, background_tasks=background - ) + spawn_set_ai_responding_bg(chat_id=1, user_id=None, background_tasks=background) return background bg = asyncio.run(_run()) diff --git a/surfsense_web/app/(home)/free/page.tsx b/surfsense_web/app/(home)/free/page.tsx index 4512f3396..5cea9b6d2 100644 --- a/surfsense_web/app/(home)/free/page.tsx +++ b/surfsense_web/app/(home)/free/page.tsx @@ -221,10 +221,7 @@ export default async function FreeHubPage() { <Separator className="my-12 max-w-4xl mx-auto" /> {/* In-content ad: above the model table */} - <aside - aria-label="Advertisement" - className="max-w-4xl mx-auto mb-8 min-h-[100px]" - > + <aside aria-label="Advertisement" className="max-w-4xl mx-auto mb-8 min-h-[100px]"> <AdUnit slot={ADSENSE_SLOTS.freeHubInContent} /> </aside> @@ -353,10 +350,7 @@ export default async function FreeHubPage() { <Separator className="my-12 max-w-4xl mx-auto" /> {/* In-content ad: after CTA, before FAQ */} - <aside - aria-label="Advertisement" - className="max-w-3xl mx-auto my-8 min-h-[100px]" - > + <aside aria-label="Advertisement" className="max-w-3xl mx-auto my-8 min-h-[100px]"> <AdUnit slot={ADSENSE_SLOTS.freeHubBeforeFaq} /> </aside> diff --git a/surfsense_web/app/(home)/privacy/page.tsx b/surfsense_web/app/(home)/privacy/page.tsx index 22833ae4a..cc7f64bee 100644 --- a/surfsense_web/app/(home)/privacy/page.tsx +++ b/surfsense_web/app/(home)/privacy/page.tsx @@ -37,9 +37,9 @@ export default function PrivacyPolicy() { </p> <p className="mt-4"> By accessing or using the Service, you acknowledge that you have read and understood - this Privacy Policy. If you do not agree with our policies and practices, do not use - the Service. We may modify this policy from time to time; material changes will be - reflected by updating the "Last updated" date above. + this Privacy Policy. If you do not agree with our policies and practices, do not use the + Service. We may modify this policy from time to time; material changes will be reflected + by updating the "Last updated" date above. </p> </section> @@ -71,9 +71,9 @@ export default function PrivacyPolicy() { Notion, Confluence, GitHub, and others) under the scopes you authorize. </li> <li> - <strong>Billing Data</strong> includes information necessary to process payments - (such as transaction identifiers and credit balances). Card details are handled by - our payment processor and are not stored on our servers. + <strong>Billing Data</strong> includes information necessary to process payments (such + as transaction identifiers and credit balances). Card details are handled by our + payment processor and are not stored on our servers. </li> <li> <strong>Technical Data</strong> includes internet protocol (IP) address, browser type @@ -126,8 +126,8 @@ export default function PrivacyPolicy() { incidents. </li> <li> - To communicate with you about product updates, security notices, support requests, - and (with your consent where required) marketing. + To communicate with you about product updates, security notices, support requests, and + (with your consent where required) marketing. </li> <li> To serve and measure advertising on pages where ads are shown (currently, our free @@ -141,8 +141,8 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">4. Cookies and Tracking Technologies</h2> <p> We and our partners use cookies, local storage, and similar technologies to operate the - Service, remember your preferences, measure usage, and serve advertising. The - categories include: + Service, remember your preferences, measure usage, and serve advertising. The categories + include: </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> @@ -179,9 +179,9 @@ export default function PrivacyPolicy() { </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> - Google, as a third-party vendor, uses cookies (including the DoubleClick DART - cookie) to serve ads to you based on your visits to our Service and other websites - on the Internet. + Google, as a third-party vendor, uses cookies (including the DoubleClick DART cookie) + to serve ads to you based on your visits to our Service and other websites on the + Internet. </li> <li> Google's use of advertising cookies enables it and its partners to serve ads to you @@ -195,14 +195,12 @@ export default function PrivacyPolicy() { <a href="https://www.youronlinechoices.com/">youronlinechoices.com</a> (EU). </li> <li> - For users in the European Economic Area, the United Kingdom, and Switzerland, we - use a Google-certified Consent Management Platform to obtain your consent for - personalized advertising before such cookies are set. You may change or withdraw - your consent at any time through the consent banner. - </li> - <li> - We do not knowingly serve personalized advertising to children. See Section 11. + For users in the European Economic Area, the United Kingdom, and Switzerland, we use a + Google-certified Consent Management Platform to obtain your consent for personalized + advertising before such cookies are set. You may change or withdraw your consent at + any time through the consent banner. </li> + <li>We do not knowingly serve personalized advertising to children. See Section 11.</li> </ul> <p className="mt-4"> For more information about how Google uses data when you use our Service, see{" "} @@ -217,8 +215,8 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">6. Data Security</h2> <p> We implement technical and organizational measures designed to protect your personal - data against accidental loss, unauthorized access, alteration, and disclosure. Access - to personal data is limited to personnel who need it to operate the Service. + data against accidental loss, unauthorized access, alteration, and disclosure. Access to + personal data is limited to personnel who need it to operate the Service. </p> <p className="mt-4"> No system can be guaranteed to be fully secure. We cannot guarantee that personal data @@ -232,10 +230,10 @@ export default function PrivacyPolicy() { <p> We retain personal data only for as long as necessary to provide the Service and to comply with our legal, accounting, and reporting obligations. Account data is retained - for the life of your account; you can request deletion at any time. Aggregated data - that no longer identifies you may be retained indefinitely for analytics and product - improvement purposes. Anonymous chat sessions on our free pages are not retained in - any user-linked database. + for the life of your account; you can request deletion at any time. Aggregated data that + no longer identifies you may be retained indefinitely for analytics and product + improvement purposes. Anonymous chat sessions on our free pages are not retained in any + user-linked database. </p> </section> @@ -243,8 +241,7 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">8. Third-Party Services</h2> <p> We rely on the following categories of third-party processors and providers to operate - the Service. Each is bound by its own privacy policy, which we encourage you to - review: + the Service. Each is bound by its own privacy policy, which we encourage you to review: </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> @@ -261,9 +258,9 @@ export default function PrivacyPolicy() { <strong>Advertising</strong>: Google AdSense (see Section 5). </li> <li> - <strong>Large language model providers</strong>: OpenAI, Anthropic, Google, and - other LLM providers process the prompts and content you submit to the Service in - order to generate responses. + <strong>Large language model providers</strong>: OpenAI, Anthropic, Google, and other + LLM providers process the prompts and content you submit to the Service in order to + generate responses. </li> <li> <strong>Integration providers</strong>: When you explicitly connect a third-party @@ -278,9 +275,7 @@ export default function PrivacyPolicy() { </section> <section className="mb-8"> - <h2 className="text-2xl font-semibold mb-4"> - 9. Your Legal Rights (Including GDPR) - </h2> + <h2 className="text-2xl font-semibold mb-4">9. Your Legal Rights (Including GDPR)</h2> <p> Subject to applicable law, you have the following rights in relation to your personal data: @@ -314,17 +309,17 @@ export default function PrivacyPolicy() { </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> - The right to know what categories of personal information we have collected about - you and how it is used and shared. + The right to know what categories of personal information we have collected about you + and how it is used and shared. </li> <li>The right to delete personal information we have collected from you.</li> <li>The right to correct inaccurate personal information.</li> <li> The right to opt out of the "sale" or "sharing" of personal information for cross-context behavioral advertising. We do not sell personal data; however, - advertising cookies set by Google AdSense may be considered "sharing" under - California law. To opt out, you can use the consent controls described in Section 5 - or enable a Global Privacy Control (GPC) signal in your browser, which we honor. + advertising cookies set by Google AdSense may be considered "sharing" under California + law. To opt out, you can use the consent controls described in Section 5 or enable a + Global Privacy Control (GPC) signal in your browser, which we honor. </li> <li>The right not to be discriminated against for exercising your privacy rights.</li> </ul> @@ -337,33 +332,32 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">11. Children's Privacy</h2> <p> The Service is not directed to children under 13 (or under 16 in the EEA, UK, and - Switzerland). We do not knowingly collect personal data from children. If you believe - a child has provided us with personal data, please contact us and we will take steps - to delete it. We do not knowingly serve personalized advertising to children. + Switzerland). We do not knowingly collect personal data from children. If you believe a + child has provided us with personal data, please contact us and we will take steps to + delete it. We do not knowingly serve personalized advertising to children. </p> </section> <section className="mb-8"> <h2 className="text-2xl font-semibold mb-4">12. Changes to This Policy</h2> <p> - We may update this Privacy Policy from time to time to reflect changes in our - practices, technology, legal requirements, or for other operational reasons. When we - make material changes, we will update the "Last updated" date at the top of this page - and, where appropriate, provide additional notice (such as an in-product notification - or email). Your continued use of the Service after the updated policy becomes - effective constitutes your acceptance of the revised policy. + We may update this Privacy Policy from time to time to reflect changes in our practices, + technology, legal requirements, or for other operational reasons. When we make material + changes, we will update the "Last updated" date at the top of this page and, where + appropriate, provide additional notice (such as an in-product notification or email). + Your continued use of the Service after the updated policy becomes effective constitutes + your acceptance of the revised policy. </p> </section> <section className="mb-8"> <h2 className="text-2xl font-semibold mb-4">13. Contact Us</h2> <p> - If you have questions about this Privacy Policy or our privacy practices, or if you - want to exercise any of your rights, please contact us at: + If you have questions about this Privacy Policy or our privacy practices, or if you want + to exercise any of your rights, please contact us at: </p> <p className="mt-2"> - <strong>Email:</strong>{" "} - <a href="mailto:rohan@surfsense.com">rohan@surfsense.com</a> + <strong>Email:</strong> <a href="mailto:rohan@surfsense.com">rohan@surfsense.com</a> </p> </section> </div> diff --git a/surfsense_web/app/api/zero/query/route.ts b/surfsense_web/app/api/zero/query/route.ts index 8caac9cd4..0e64c932f 100644 --- a/surfsense_web/app/api/zero/query/route.ts +++ b/surfsense_web/app/api/zero/query/route.ts @@ -1,10 +1,10 @@ import { mustGetQuery } from "@rocicorp/zero"; import { handleQueryRequest } from "@rocicorp/zero/server"; import { NextResponse } from "next/server"; +import { BACKEND_URL } from "@/lib/env-config"; import type { Context } from "@/types/zero"; import { queries } from "@/zero/queries"; import { schema } from "@/zero/schema"; -import { BACKEND_URL } from "@/lib/env-config"; const backendURL = BACKEND_URL; diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx index 219552a1a..6504af5a4 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx @@ -16,10 +16,7 @@ interface AutomationEditContentProps { * structure but gates on ``canUpdate`` instead of ``canRead``: a user who * can read but not update is bounced to the access-denied panel. */ -export function AutomationEditContent({ - searchSpaceId, - automationId, -}: AutomationEditContentProps) { +export function AutomationEditContent({ searchSpaceId, automationId }: AutomationEditContentProps) { const perms = useAutomationPermissions(); const validId = Number.isInteger(automationId) && automationId > 0; const { data: automation, isLoading, error } = useAutomation(validId ? automationId : undefined); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx index 86b355838..9b950608e 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx @@ -9,10 +9,7 @@ import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; -import { - type Automation, - automationUpdateRequest, -} from "@/contracts/types/automation.types"; +import { type Automation, automationUpdateRequest } from "@/contracts/types/automation.types"; interface AutomationEditFormProps { automation: Automation; diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 6cd95a79c..06a069a4c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -69,11 +69,11 @@ import { documentsApiService } from "@/lib/apis/documents-api.service"; import { getBearerToken } from "@/lib/auth-utils"; import { type ChatFlow, classifyChatError } from "@/lib/chat/chat-error-classifier"; import { tagPreAcceptSendFailure, toHttpResponseError } from "@/lib/chat/chat-request-errors"; +import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { convertToThreadMessage, reconcileInterruptedAssistantMessages, } from "@/lib/chat/message-utils"; -import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { isPodcastGenerating, looksLikePodcastRequest, @@ -110,6 +110,7 @@ import { extractUserTurnForNewChatApi, type NewChatUserImagePayload, } from "@/lib/chat/user-turn-api-parts"; +import { BACKEND_URL } from "@/lib/env-config"; import { NotFoundError } from "@/lib/error"; import { trackChatBlocked, @@ -119,7 +120,7 @@ import { trackChatResponseReceived, } from "@/lib/posthog/events"; import Loading from "../loading"; -import { BACKEND_URL } from "@/lib/env-config"; + const MobileEditorPanel = dynamic( () => import("@/components/editor-panel/editor-panel").then((m) => ({ @@ -1977,14 +1978,12 @@ export default function NewChatPage() { mentioned_folder_ids: regenerateFolderIds.length > 0 ? regenerateFolderIds : undefined, mentioned_connector_ids: regenerateConnectors.length > 0 ? regenerateConnectors.map((d) => d.id) : undefined, - mentioned_connectors: - regenerateConnectors.length > 0 ? regenerateConnectors : undefined, + mentioned_connectors: regenerateConnectors.length > 0 ? regenerateConnectors : undefined, // Full mention metadata for the regenerate-specific // source list. Only meaningful for edit (the BE only // re-persists a user row when ``user_query`` is set); // reload reuses the original turn's mentioned_documents. - mentioned_documents: - sourceMentionedDocs.length > 0 ? sourceMentionedDocs : undefined, + mentioned_documents: sourceMentionedDocs.length > 0 ? sourceMentionedDocs : undefined, }; if (isEdit) { requestBody.user_images = editExtras?.userImages ?? []; diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx index 17c1dd121..3bc2459c1 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx @@ -31,7 +31,7 @@ import { deleteMemberMutationAtom, updateMemberMutationAtom, } from "@/atoms/members/members-mutation.atoms"; -import { membersAtom, myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; +import { canPerform, membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; import { AlertDialog, AlertDialogAction, diff --git a/surfsense_web/atoms/members/members-query.atoms.ts b/surfsense_web/atoms/members/members-query.atoms.ts index f8e4b2cf6..c1f507ff5 100644 --- a/surfsense_web/atoms/members/members-query.atoms.ts +++ b/surfsense_web/atoms/members/members-query.atoms.ts @@ -42,11 +42,11 @@ export const myAccessAtom = atomWithQuery((get) => { /** * Helper function to check if the current user has a specific permission. - * + * * @param access - The access object from useAtomValue(myAccessAtom) * @param permission - The permission string to check * @returns boolean indicating if the user has the permission - * + * * @example * const access = useAtomValue(myAccessAtom); * if (canPerform(access, 'manage_members')) { ... } @@ -63,10 +63,10 @@ export function canPerform( /** * Hook wrapper for canPerform that reads from myAccessAtom internally. * Use this if you want to avoid calling useAtomValue(myAccessAtom) separately. - * + * * @param permission - The permission string to check * @returns boolean indicating if the user has the permission - * + * * @example * const canManageMembers = usePermissionGate('manage_members'); */ diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx index f6c91e8bf..fd24600c2 100644 --- a/surfsense_web/components/assistant-ui/assistant-message.tsx +++ b/surfsense_web/components/assistant-ui/assistant-message.tsx @@ -13,8 +13,8 @@ import { CheckIcon, ClipboardPaste, CopyIcon, - DownloadIcon, Dot, + DownloadIcon, ExternalLink, Globe, MessageCircleReply, diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx index 01b86a538..a9231d846 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx @@ -6,14 +6,13 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { EnumConnectorName } from "@/contracts/enums/connector"; import { useApiKey } from "@/hooks/use-api-key"; +import { BACKEND_URL } from "@/lib/env-config"; import { getConnectorBenefits } from "../connector-benefits"; import type { ConnectFormProps } from "../index"; -import { BACKEND_URL } from "@/lib/env-config"; const PLUGIN_RELEASES_URL = "https://github.com/MODSetter/SurfSense/releases?q=obsidian&expanded=true"; - /** * Obsidian connect form for the plugin-only architecture. * diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx index fe6724ed8..4de8500a6 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx @@ -9,8 +9,8 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { authenticatedFetch } from "@/lib/auth-utils"; -import type { ConnectorConfigProps } from "../index"; import { BACKEND_URL } from "@/lib/env-config"; +import type { ConnectorConfigProps } from "../index"; export interface CirclebackConfigProps extends ConnectorConfigProps { onNameChange?: (name: string) => void; } diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 3e9e9bb27..2b86daf65 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -23,6 +23,7 @@ import { LIVE_CONNECTOR_TYPES } from "../../constants/connector-constants"; import { getConnectorDisplayName } from "../../tabs/all-connectors-tab"; import { MCPServiceConfig } from "../components/mcp-service-config"; import { getConnectorConfigComponent } from "../index"; + const VISION_LLM_CONNECTOR_TYPES = new Set<SearchSourceConnector["connector_type"]>([ EnumConnectorName.GOOGLE_DRIVE_CONNECTOR, EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx index 27e102d7e..05b684397 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx @@ -18,6 +18,7 @@ import { cn } from "@/lib/utils"; import { LIVE_CONNECTOR_TYPES } from "../constants/connector-constants"; import { useConnectorStatus } from "../hooks/use-connector-status"; import { getConnectorDisplayName } from "../tabs/all-connectors-tab"; + interface ConnectorAccountsListViewProps { connectorType: string; connectorTitle: string; diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index c0d9d9212..52e015c56 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -97,7 +97,12 @@ interface InlineMentionEditorProps { onActionClose?: () => void; onSubmit?: () => void; onChange?: (text: string, docs: MentionedDocument[]) => void; - onDocumentRemove?: (docId: number, docType?: string, kind?: MentionKind, connectorType?: string) => void; + onDocumentRemove?: ( + docId: number, + docType?: string, + kind?: MentionKind, + connectorType?: string + ) => void; onKeyDown?: (e: React.KeyboardEvent) => void; disabled?: boolean; className?: string; @@ -171,9 +176,10 @@ const MentionElement: FC<PlateElementProps<MentionElementNode>> = ({ {isFolder ? ( <FolderIcon className="h-3 w-3" /> ) : isConnector ? ( - getConnectorIcon(element.connector_type ?? element.document_type ?? "UNKNOWN", "h-3 w-3") ?? ( - <PlugIcon className="h-3 w-3" /> - ) + (getConnectorIcon( + element.connector_type ?? element.document_type ?? "UNKNOWN", + "h-3 w-3" + ) ?? <PlugIcon className="h-3 w-3" />) ) : ( getConnectorIcon(element.document_type ?? "UNKNOWN", "h-3 w-3") )} @@ -357,7 +363,11 @@ function getSelectionAnchorRect(root: HTMLElement | null): SuggestionAnchorRect const rect = range.getClientRects()[0] ?? range.getBoundingClientRect(); if (rect.width > 0 || rect.height > 0) return rectToAnchor(rect); - if (range.collapsed && range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset > 0) { + if ( + range.collapsed && + range.startContainer.nodeType === Node.TEXT_NODE && + range.startOffset > 0 + ) { const fallbackRange = range.cloneRange(); fallbackRange.setStart(range.startContainer, range.startOffset - 1); fallbackRange.setEnd(range.startContainer, range.startOffset); diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 5c5f99940..9abcfbb49 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -68,11 +68,6 @@ import { import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { UserMessage } from "@/components/assistant-ui/user-message"; import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; -import { - DocumentMentionPicker, - promoteRecentMention, - type DocumentMentionPickerRef, -} from "../new-chat/document-mention-picker"; import { PromptPicker, type PromptPickerRef } from "@/components/new-chat/prompt-picker"; import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; @@ -112,6 +107,11 @@ import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { slideoutOpenedTickAtom } from "@/lib/layout-events"; import { cn } from "@/lib/utils"; +import { + DocumentMentionPicker, + type DocumentMentionPickerRef, + promoteRecentMention, +} from "../new-chat/document-mention-picker"; const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mention docs"; @@ -601,21 +601,24 @@ const Composer: FC = () => { } }, []); - const handleActionTrigger = useCallback((trigger: SuggestionTriggerInfo) => { - const anchorPoint = getComposerSuggestionAnchorPoint( - trigger.anchorRect, - clipboardInitialText ? "bottom" : "top" - ); - if (!anchorPoint) { - setShowPromptPicker(false); - setActionQuery(""); - setSuggestionAnchorPoint(null); - return; - } - setSuggestionAnchorPoint((current) => current ?? anchorPoint); - setShowPromptPicker(true); - setActionQuery(trigger.query); - }, [clipboardInitialText]); + const handleActionTrigger = useCallback( + (trigger: SuggestionTriggerInfo) => { + const anchorPoint = getComposerSuggestionAnchorPoint( + trigger.anchorRect, + clipboardInitialText ? "bottom" : "top" + ); + if (!anchorPoint) { + setShowPromptPicker(false); + setActionQuery(""); + setSuggestionAnchorPoint(null); + return; + } + setSuggestionAnchorPoint((current) => current ?? anchorPoint); + setShowPromptPicker(true); + setActionQuery(trigger.query); + }, + [clipboardInitialText] + ); const handleActionClose = useCallback(() => { if (showPromptPicker) { @@ -754,7 +757,12 @@ const Composer: FC = () => { ]); const handleDocumentRemove = useCallback( - (docId: number, docType?: string, kind?: "doc" | "folder" | "connector", connectorType?: string) => { + ( + docId: number, + docType?: string, + kind?: "doc" | "folder" | "connector", + connectorType?: string + ) => { setMentionedDocuments((prev) => { const removedKey = getMentionDocKey({ id: docId, @@ -768,27 +776,30 @@ const Composer: FC = () => { [setMentionedDocuments] ); - const handleDocumentsMention = useCallback((mentions: MentionedDocumentInfo[]) => { - const parsedSearchSpaceId = Number(search_space_id); - const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? []; - const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc))); + const handleDocumentsMention = useCallback( + (mentions: MentionedDocumentInfo[]) => { + const parsedSearchSpaceId = Number(search_space_id); + const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? []; + const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc))); - for (const mention of mentions) { - const key = getMentionDocKey(mention); - if (editorDocKeys.has(key)) continue; - editorRef.current?.insertMentionChip(mention); - if (Number.isFinite(parsedSearchSpaceId)) { - promoteRecentMention(parsedSearchSpaceId, mention); + for (const mention of mentions) { + const key = getMentionDocKey(mention); + if (editorDocKeys.has(key)) continue; + editorRef.current?.insertMentionChip(mention); + if (Number.isFinite(parsedSearchSpaceId)) { + promoteRecentMention(parsedSearchSpaceId, mention); + } + // Track within the loop so a duplicate-in-batch can't double-insert. + editorDocKeys.add(key); } - // Track within the loop so a duplicate-in-batch can't double-insert. - editorDocKeys.add(key); - } - // Atom is reconciled by ``handleEditorChange`` via the editor's - // onChange — no second write path here. - setMentionQuery(""); - setSuggestionAnchorPoint(null); - }, [search_space_id]); + // Atom is reconciled by ``handleEditorChange`` via the editor's + // onChange — no second write path here. + setMentionQuery(""); + setSuggestionAnchorPoint(null); + }, + [search_space_id] + ); useEffect(() => { const editor = editorRef.current; diff --git a/surfsense_web/components/assistant-ui/user-message.tsx b/surfsense_web/components/assistant-ui/user-message.tsx index b30db5f69..5c90dce55 100644 --- a/surfsense_web/components/assistant-ui/user-message.tsx +++ b/surfsense_web/components/assistant-ui/user-message.tsx @@ -104,9 +104,9 @@ const UserTextPart: FC = () => { const icon = isFolder ? ( <FolderIcon className="size-3.5" /> ) : isConnector ? ( - getConnectorIcon(segment.doc.connector_type, "size-3.5") ?? ( + (getConnectorIcon(segment.doc.connector_type, "size-3.5") ?? ( <Plug className="size-3.5" /> - ) + )) ) : ( getConnectorIcon(segment.doc.document_type ?? "UNKNOWN", "size-3.5") ); @@ -123,7 +123,9 @@ const UserTextPart: FC = () => { : segment.doc.title } onClick={ - isFolder || isConnector ? undefined : () => handleOpenDoc(segment.doc.id, segment.doc.title) + isFolder || isConnector + ? undefined + : () => handleOpenDoc(segment.doc.id, segment.doc.title) } className="mx-0.5" /> diff --git a/surfsense_web/components/editor-panel/editor-panel.tsx b/surfsense_web/components/editor-panel/editor-panel.tsx index 2baffaf0f..534ff9daa 100644 --- a/surfsense_web/components/editor-panel/editor-panel.tsx +++ b/surfsense_web/components/editor-panel/editor-panel.tsx @@ -34,6 +34,7 @@ import { useElectronAPI } from "@/hooks/use-platform"; import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils"; import { inferMonacoLanguageFromPath } from "@/lib/editor-language"; import { BACKEND_URL } from "@/lib/env-config"; + const PlateEditor = dynamic( () => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })), { ssr: false, loading: () => <EditorPanelSkeleton /> } diff --git a/surfsense_web/components/free-chat/anonymous-chat.tsx b/surfsense_web/components/free-chat/anonymous-chat.tsx index 28ee1f6f0..aff58f7bc 100644 --- a/surfsense_web/components/free-chat/anonymous-chat.tsx +++ b/surfsense_web/components/free-chat/anonymous-chat.tsx @@ -6,11 +6,12 @@ import { Button } from "@/components/ui/button"; import type { AnonModel, AnonQuotaResponse } from "@/contracts/types/anonymous-chat.types"; import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service"; import { readSSEStream } from "@/lib/chat/streaming-state"; +import { BACKEND_URL } from "@/lib/env-config"; import { trackAnonymousChatMessageSent } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; import { QuotaBar } from "./quota-bar"; import { QuotaWarningBanner } from "./quota-warning-banner"; -import { BACKEND_URL } from "@/lib/env-config"; + interface Message { id: string; role: "user" | "assistant"; @@ -80,19 +81,16 @@ export function AnonymousChat({ model }: AnonymousChatProps) { content: m.content, })); - const response = await fetch( - `${BACKEND_URL}/api/v1/public/anon-chat/stream`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ - model_slug: modelSlug, - messages: chatHistory, - }), - signal: controller.signal, - } - ); + const response = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/stream`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ + model_slug: modelSlug, + messages: chatHistory, + }), + signal: controller.signal, + }); if (!response.ok) { if (response.status === 429) { diff --git a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx index 0aed0db61..b90a3b2a9 100644 --- a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx +++ b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx @@ -193,11 +193,7 @@ export function SearchSpaceAvatar({ // If delete or settings handlers are provided, expose them through a dropdown menu. if (onDelete || onSettings) { - const trigger = ( - <DropdownMenuTrigger asChild> - {avatarButton(true)} - </DropdownMenuTrigger> - ); + const trigger = <DropdownMenuTrigger asChild>{avatarButton(true)}</DropdownMenuTrigger>; return ( <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}> diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index ca90ba9b9..757ee2fc2 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -78,11 +78,11 @@ import { foldersApiService } from "@/lib/apis/folders-api.service"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; +import { BACKEND_URL } from "@/lib/env-config"; import { uploadFolderScan } from "@/lib/folder-sync-upload"; import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; import { queries } from "@/zero/queries/index"; import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel"; -import { BACKEND_URL } from "@/lib/env-config"; const DesktopLocalTabContent = dynamic( () => import("./DesktopLocalTabContent").then((mod) => mod.DesktopLocalTabContent), diff --git a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx index ef51eee3c..34cf707b0 100644 --- a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx +++ b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx @@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils"; import { BACKEND_URL } from "@/lib/env-config"; + const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB interface DocumentContent { diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index 3fdf48875..7a30f487b 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -117,7 +117,10 @@ const ComposerSuggestionItem = React.forwardRef< )); ComposerSuggestionItem.displayName = "ComposerSuggestionItem"; -function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) { +function ComposerSuggestionSeparator({ + className, + ...props +}: React.ComponentProps<typeof Separator>) { return ( <div className={cn("my-0.5 px-2.5", className)}> <Separator className="bg-popover-border" {...props} /> diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c26e51922..c2a0f1665 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -2,6 +2,7 @@ import { useQuery as useZeroQuery } from "@rocicorp/zero/react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { BookOpen, ChevronLeft, @@ -22,7 +23,6 @@ import { useState, } from "react"; import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; -import { useAtomValue } from "jotai"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; import { getConnectorTitle } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; import { getConnectorDisplayName } from "@/components/assistant-ui/connector-popup/tabs/all-connectors-tab"; @@ -178,7 +178,9 @@ function useDebounced<T>(value: T, delay = DEBOUNCE_MS) { return debounced; } -function makeDocMention(doc: Pick<Document, "id" | "title" | "document_type">): MentionedDocumentInfo { +function makeDocMention( + doc: Pick<Document, "id" | "title" | "document_type"> +): MentionedDocumentInfo { return { id: doc.id, title: doc.title, @@ -187,9 +189,10 @@ function makeDocMention(doc: Pick<Document, "id" | "title" | "document_type">): }; } -function makeFolderMention( - folder: { id: number; title: string } -): Extract<MentionedDocumentInfo, { kind: "folder" }> { +function makeFolderMention(folder: { + id: number; + title: string; +}): Extract<MentionedDocumentInfo, { kind: "folder" }> { return { id: folder.id, title: folder.title, @@ -319,24 +322,24 @@ export const DocumentMentionPicker = forwardRef< useEffect(() => { if (currentPage !== 0) return; - const combinedDocs: Pick<Document, "id" | "title" | "document_type">[] = []; + const combinedDocs: Pick<Document, "id" | "title" | "document_type">[] = []; - if (surfsenseDocs?.items) { - for (const doc of surfsenseDocs.items) { - combinedDocs.push({ - id: doc.id, - title: doc.title, - document_type: "SURFSENSE_DOCS", - }); - } + if (surfsenseDocs?.items) { + for (const doc of surfsenseDocs.items) { + combinedDocs.push({ + id: doc.id, + title: doc.title, + document_type: "SURFSENSE_DOCS", + }); } + } - if (titleSearchResults?.items) { - combinedDocs.push(...titleSearchResults.items); - setHasMore(titleSearchResults.has_more); - } + if (titleSearchResults?.items) { + combinedDocs.push(...titleSearchResults.items); + setHasMore(titleSearchResults.has_more); + } - setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); + setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm]); const loadNextPage = useCallback(async () => { @@ -352,9 +355,11 @@ export const DocumentMentionPicker = forwardRef< page_size: PAGE_SIZE, ...(isSearchValid ? { title: debouncedSearch.trim() } : {}), }; - const response: SearchDocumentTitlesResponse = await documentsApiService.searchDocumentTitles({ - queryParams, - }); + const response: SearchDocumentTitlesResponse = await documentsApiService.searchDocumentTitles( + { + queryParams, + } + ); setAccumulatedDocuments((prev) => [...prev, ...response.items]); setHasMore(response.has_more); @@ -431,7 +436,13 @@ export const DocumentMentionPicker = forwardRef< ) .filter((mention): mention is MentionedDocumentInfo => mention !== null) .slice(0, RECENTS_LIMIT), - [activeConnectors, hasHydratedRecentDocs, recentMentions, recentValidationDocuments, zeroFolders] + [ + activeConnectors, + hasHydratedRecentDocs, + recentMentions, + recentValidationDocuments, + zeroFolders, + ] ); const selectedKeys = useMemo( @@ -460,47 +471,46 @@ export const DocumentMentionPicker = forwardRef< [visibleRecentMentions, selectedKeys] ); - const rootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>( - () => { - const nodes: ComposerSuggestionNode<ResourceNodeValue>[] = [...recentRootNodes]; - if (showSurfsenseDocsRoot) { - nodes.push({ - id: "surfsense-docs", - label: "SurfSense Docs", - subtitle: "Browse product documentation", - icon: <BookOpen className="size-4" />, - type: "branch", - value: { kind: "view", view: { kind: "surfsense-docs" } }, - }); - } - nodes.push( - { - id: "files-folders", - label: "Files & Folders", - subtitle: "Browse your knowledge base", - icon: <Files className="size-4" />, - type: "branch", - value: { kind: "view", view: { kind: "files-folders" } }, - }, - { - id: "connectors", - label: "Connectors", - subtitle: activeConnectors.length - ? "Choose the exact account for tool use" - : "No connected accounts yet", + const rootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(() => { + const nodes: ComposerSuggestionNode<ResourceNodeValue>[] = [...recentRootNodes]; + if (showSurfsenseDocsRoot) { + nodes.push({ + id: "surfsense-docs", + label: "SurfSense Docs", + subtitle: "Browse product documentation", + icon: <BookOpen className="size-4" />, + type: "branch", + value: { kind: "view", view: { kind: "surfsense-docs" } }, + }); + } + nodes.push( + { + id: "files-folders", + label: "Files & Folders", + subtitle: "Browse your knowledge base", + icon: <Files className="size-4" />, + type: "branch", + value: { kind: "view", view: { kind: "files-folders" } }, + }, + { + id: "connectors", + label: "Connectors", + subtitle: activeConnectors.length + ? "Choose the exact account for tool use" + : "No connected accounts yet", icon: <Unplug className="size-4" />, - type: "branch", - disabled: activeConnectors.length === 0, - value: { kind: "view", view: { kind: "connectors" } }, - } - ); - return nodes; - }, - [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot] - ); + type: "branch", + disabled: activeConnectors.length === 0, + value: { kind: "view", view: { kind: "connectors" } }, + } + ); + return nodes; + }, [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot]); const searchNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(() => { - const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch).trim().toLowerCase(); + const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch) + .trim() + .toLowerCase(); const docNodes = actualDocuments.map((doc) => { const mention = makeDocMention(doc); return { @@ -619,7 +629,9 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: getConnectorDisplayName(connector.name), subtitle: `${view.title} account`, - icon: getConnectorIcon(connector.connector_type, "size-4") ?? <Unplug className="size-4" />, + icon: getConnectorIcon(connector.connector_type, "size-4") ?? ( + <Unplug className="size-4" /> + ), type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -733,7 +745,7 @@ export const DocumentMentionPicker = forwardRef< icon={ <span className="-ml-0.5 flex size-4.5 items-center justify-center"> <ChevronLeft className="size-3.5" /> - </span> + </span> } > <span className="flex-1 truncate">{title}</span> @@ -759,7 +771,7 @@ export const DocumentMentionPicker = forwardRef< return ( <Fragment key={node.id}> {showRecentsSeparator ? <ComposerSuggestionSeparator /> : null} - <ComposerSuggestionItem + <ComposerSuggestionItem ref={navigator.getItemRef(index)} icon={node.icon} selected={index === navigator.highlightedIndex} @@ -776,11 +788,11 @@ export const DocumentMentionPicker = forwardRef< {node.subtitle} </span> ) : null} - </span> + </span> {node.type === "branch" ? ( <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" /> ) : null} - </ComposerSuggestionItem> + </ComposerSuggestionItem> </Fragment> ); })} diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index 986a5d608..65bf0a889 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -129,7 +129,9 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct {isLoading ? ( <ComposerSuggestionSkeleton rows={8} mobileRows={8} /> ) : isError ? ( - <ComposerSuggestionMessage variant="destructive">Failed to load prompts</ComposerSuggestionMessage> + <ComposerSuggestionMessage variant="destructive"> + Failed to load prompts + </ComposerSuggestionMessage> ) : filtered.length === 0 ? ( <ComposerSuggestionMessage>No matching prompts</ComposerSuggestionMessage> ) : ( diff --git a/surfsense_web/components/settings/general-settings-manager.tsx b/surfsense_web/components/settings/general-settings-manager.tsx index 23398ad4d..a308acfad 100644 --- a/surfsense_web/components/settings/general-settings-manager.tsx +++ b/surfsense_web/components/settings/general-settings-manager.tsx @@ -12,9 +12,9 @@ import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { BACKEND_URL } from "@/lib/env-config"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { Spinner } from "../ui/spinner"; -import { BACKEND_URL } from "@/lib/env-config"; interface GeneralSettingsManagerProps { searchSpaceId: number; diff --git a/surfsense_web/components/settings/prompt-config-manager.tsx b/surfsense_web/components/settings/prompt-config-manager.tsx index 71cfcb971..997749a2a 100644 --- a/surfsense_web/components/settings/prompt-config-manager.tsx +++ b/surfsense_web/components/settings/prompt-config-manager.tsx @@ -20,10 +20,7 @@ interface PromptConfigManagerProps { } export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps) { - const { - data: searchSpace, - isLoading: loading, - } = useQuery({ + const { data: searchSpace, isLoading: loading } = useQuery({ queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()), queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }), enabled: !!searchSpaceId, @@ -56,8 +53,7 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps) }); toast.success("System instructions saved successfully"); } catch (error: unknown) { - const message = - error instanceof Error ? error.message : "Failed to save system instructions"; + const message = error instanceof Error ? error.message : "Failed to save system instructions"; console.error("Error saving system instructions:", error); toast.error(message); } diff --git a/surfsense_web/components/tool-ui/generate-podcast.tsx b/surfsense_web/components/tool-ui/generate-podcast.tsx index 866c0082d..2a62785e8 100644 --- a/surfsense_web/components/tool-ui/generate-podcast.tsx +++ b/surfsense_web/components/tool-ui/generate-podcast.tsx @@ -16,6 +16,7 @@ import { baseApiService } from "@/lib/apis/base-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; import { clearActivePodcastTaskId, setActivePodcastTaskId } from "@/lib/chat/podcast-state"; import { BACKEND_URL } from "@/lib/env-config"; + /** * Zod schemas for runtime validation */ @@ -193,10 +194,10 @@ function PodcastPlayer({ } else { // Authenticated view - fetch audio and details in parallel const [audioResponse, details] = await Promise.all([ - authenticatedFetch( - `${BACKEND_URL}/api/v1/podcasts/${podcastId}/audio`, - { method: "GET", signal: controller.signal } - ), + authenticatedFetch(`${BACKEND_URL}/api/v1/podcasts/${podcastId}/audio`, { + method: "GET", + signal: controller.signal, + }), baseApiService.get<unknown>(`/api/v1/podcasts/${podcastId}`), ]); diff --git a/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx b/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx index 00f9db23f..1db8dabb0 100644 --- a/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx +++ b/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx @@ -10,6 +10,7 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { baseApiService } from "@/lib/apis/base-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { BACKEND_URL } from "@/lib/env-config"; import { compileCheck, compileToComponent } from "@/lib/remotion/compile-check"; import { FPS } from "@/lib/remotion/constants"; import { @@ -19,7 +20,6 @@ import { type CompiledSlide, } from "./combined-player"; import { getPptxExportErrorToast, getVideoDownloadErrorToast } from "./errors"; -import { BACKEND_URL } from "@/lib/env-config"; const GenerateVideoPresentationArgsSchema = z.object({ source_content: z.string(), diff --git a/surfsense_web/hooks/use-search-source-connectors.ts b/surfsense_web/hooks/use-search-source-connectors.ts index bc1ec49b5..ad0db3de6 100644 --- a/surfsense_web/hooks/use-search-source-connectors.ts +++ b/surfsense_web/hooks/use-search-source-connectors.ts @@ -107,9 +107,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: setError(null); // Build URL with optional search_space_id query parameter - const url = new URL( - `${BACKEND_URL}/api/v1/search-source-connectors` - ); + const url = new URL(`${BACKEND_URL}/api/v1/search-source-connectors`); if (spaceId !== undefined) { url.searchParams.append("search_space_id", spaceId.toString()); } @@ -169,9 +167,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: ) => { try { // Add search_space_id as a query parameter - const url = new URL( - `${BACKEND_URL}/api/v1/search-source-connectors` - ); + const url = new URL(`${BACKEND_URL}/api/v1/search-source-connectors`); url.searchParams.append("search_space_id", spaceId.toString()); const response = await authenticatedFetch(url.toString(), { @@ -283,9 +279,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: } const response = await authenticatedFetch( - `${ - BACKEND_URL - }/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`, + `${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/surfsense_web/lib/auth-utils.ts b/surfsense_web/lib/auth-utils.ts index 645a6d1ba..b7dab7717 100644 --- a/surfsense_web/lib/auth-utils.ts +++ b/surfsense_web/lib/auth-utils.ts @@ -2,6 +2,7 @@ * Authentication utilities for handling token expiration and redirects */ import { BACKEND_URL } from "@/lib/env-config"; + const REDIRECT_PATH_KEY = "surfsense_redirect_path"; const BEARER_TOKEN_KEY = "surfsense_bearer_token"; const REFRESH_TOKEN_KEY = "surfsense_refresh_token"; diff --git a/surfsense_web/lib/posthog/events.ts b/surfsense_web/lib/posthog/events.ts index 687d589f9..a584f9b6f 100644 --- a/surfsense_web/lib/posthog/events.ts +++ b/surfsense_web/lib/posthog/events.ts @@ -1,6 +1,6 @@ +import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; import type { ConnectorTelemetryMeta } from "@/lib/connector-telemetry"; import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; -import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; /** * PostHog Analytics Event Definitions From c601a9b102d491f7a4b7125ca0df80922289deca Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 19:22:54 -0700 Subject: [PATCH 090/133] fix: biome errs --- surfsense_web/components/ads/ad-unit.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/surfsense_web/components/ads/ad-unit.tsx b/surfsense_web/components/ads/ad-unit.tsx index 5f5860607..cc5848a00 100644 --- a/surfsense_web/components/ads/ad-unit.tsx +++ b/surfsense_web/components/ads/ad-unit.tsx @@ -52,7 +52,8 @@ export function AdUnit({ // sets data-adsbygoogle-status="done" once it has filled a slot. if (el.getAttribute("data-adsbygoogle-status")) return; try { - (window.adsbygoogle = window.adsbygoogle || []).push({}); + window.adsbygoogle = window.adsbygoogle || []; + window.adsbygoogle.push({}); } catch { // AdSense throws if pushed before the script has loaded or on // duplicate pushes. The script processes pending pushes when it From d013617bf60e5528a384386505d31fe557be0a93 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 21:26:32 -0700 Subject: [PATCH 091/133] feat(automations): added UI and improved mentions - Added support for @-mentions in agent tasks, allowing users to reference documents, folders, and connectors directly in their queries. - Updated `run_agent_task` to resolve mentions and include them in the context passed to the agent. - Introduced new parameters in `AgentTaskActionParams` for handling mentioned document and connector IDs. - Refactored the automation edit and new components to utilize the new `AutomationBuilderForm` for a more streamlined user experience. - Removed deprecated JSON forms to simplify the automation creation process. --- .../automations/actions/agent_task/factory.py | 5 + .../automations/actions/agent_task/invoke.py | 132 ++++- .../automations/actions/agent_task/params.py | 31 ++ .../edit/automation-edit-content.tsx | 10 +- .../edit/components/automation-edit-form.tsx | 118 ----- .../components/automation-edit-header.tsx | 31 ++ .../components/builder/advanced-section.tsx | 129 +++++ .../builder/automation-builder-form.tsx | 459 ++++++++++++++++++ .../components/builder/basics-section.tsx | 42 ++ .../components/builder/builder-summary.tsx | 96 ++++ .../components/builder/form-field.tsx | 42 ++ .../components/builder/json-mode-panel.tsx | 51 ++ .../components/builder/mention-task-input.tsx | 258 ++++++++++ .../components/builder/schedule-section.tsx | 275 +++++++++++ .../components/builder/task-item.tsx | 136 ++++++ .../components/builder/task-list.tsx | 65 +++ .../components/builder/timezone-combobox.tsx | 71 +++ .../components/builder/unattended-toggle.tsx | 47 ++ .../new/automation-new-content.tsx | 12 +- .../new/components/automation-json-form.tsx | 98 ---- .../new/components/automation-new-header.tsx | 7 +- .../new-chat/document-mention-picker.tsx | 24 +- .../lib/automations/builder-schema.ts | 456 +++++++++++++++++ .../lib/automations/default-template.ts | 44 -- .../lib/automations/schedule-builder.ts | 132 +++++ 25 files changed, 2490 insertions(+), 281 deletions(-) delete mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx delete mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx create mode 100644 surfsense_web/lib/automations/builder-schema.ts delete mode 100644 surfsense_web/lib/automations/default-template.ts create mode 100644 surfsense_web/lib/automations/schedule-builder.ts diff --git a/surfsense_backend/app/automations/actions/agent_task/factory.py b/surfsense_backend/app/automations/actions/agent_task/factory.py index 18a408e13..dec75dce8 100644 --- a/surfsense_backend/app/automations/actions/agent_task/factory.py +++ b/surfsense_backend/app/automations/actions/agent_task/factory.py @@ -18,6 +18,11 @@ def build_handler(ctx: ActionContext) -> ActionHandler: ctx=ctx, query=validated.query, auto_approve_all=validated.auto_approve_all, + mentioned_document_ids=validated.mentioned_document_ids, + mentioned_folder_ids=validated.mentioned_folder_ids, + mentioned_connector_ids=validated.mentioned_connector_ids, + mentioned_connectors=validated.mentioned_connectors, + mentioned_documents=validated.mentioned_documents, ) return handle diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index 6cc92b232..fa02d263f 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -8,9 +8,13 @@ from typing import Any from langchain_core.messages import HumanMessage from langgraph.types import Command +from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.agents.new_chat.context import SurfSenseContextSchema +from app.agents.new_chat.mention_resolver import resolve_mentions, substitute_in_text from app.db import ChatVisibility, async_session_maker +from app.schemas.new_chat import MentionedDocumentInfo from ..types import ActionContext from .auto_decide import build_auto_decisions @@ -22,17 +26,118 @@ from .finalize import extract_final_assistant_message _MAX_RESUMES = 50 +def _build_connector_block(connectors: list[dict[str, Any]]) -> str | None: + """Render the ``<mentioned_connectors>`` context block (same shape as chat). + + Mirrors ``stream_new_chat`` so the agent gets the exact connector accounts + the user picked. Returns ``None`` when nothing renders. + """ + lines: list[str] = [] + for connector in connectors: + connector_id = connector.get("id") + connector_type = connector.get("connector_type") or connector.get( + "document_type" + ) + account_name = connector.get("account_name") or connector.get("title") + if connector_id is None or connector_type is None: + continue + lines.append( + f' - connector_id={connector_id}, connector_type="{connector_type}", ' + f'account_name="{account_name or ""}"' + ) + if not lines: + return None + return ( + "<mentioned_connectors>\n" + "The user selected these exact connector accounts with @. " + "These entries are selection metadata, not retrieved connector content. " + "When a connector-backed tool needs an account, use the matching " + "connector_id from this list if the tool supports connector_id:\n" + + "\n".join(lines) + + "\n</mentioned_connectors>" + ) + + +async def _resolve_mention_context( + session: AsyncSession, + *, + search_space_id: int, + query: str, + mentioned_document_ids: list[int] | None, + mentioned_folder_ids: list[int] | None, + mentioned_connector_ids: list[int] | None, + mentioned_connectors: list[MentionedDocumentInfo] | None, + mentioned_documents: list[MentionedDocumentInfo] | None, +) -> tuple[str, SurfSenseContextSchema | None]: + """Resolve @-mentions into a rewritten query + per-invocation context. + + Automation always runs in cloud filesystem mode, so we mirror the chat + ``new_chat`` flow: substitute ``@title`` tokens with canonical + ``/documents/...`` paths, prepend a ``<mentioned_connectors>`` block, and + build a ``SurfSenseContextSchema`` that ``KnowledgePriorityMiddleware`` + reads via ``runtime.context``. Returns ``(query, None)`` unchanged when + there are no mentions. + """ + has_mentions = bool( + mentioned_document_ids + or mentioned_folder_ids + or mentioned_connector_ids + or mentioned_connectors + or mentioned_documents + ) + if not has_mentions: + return query, None + + resolved = await resolve_mentions( + session, + search_space_id=search_space_id, + mentioned_documents=mentioned_documents, + mentioned_document_ids=mentioned_document_ids, + mentioned_folder_ids=mentioned_folder_ids, + ) + agent_query = substitute_in_text(query, resolved.token_to_path) + + # ``SurfSenseContextSchema.mentioned_connectors`` is typed ``list[dict]`` and + # the connector block reads dicts, so dump the pydantic chips once. + connector_dicts = [c.model_dump() for c in (mentioned_connectors or [])] + connector_block = _build_connector_block(connector_dicts) + if connector_block: + agent_query = f"{connector_block}\n\n<user_query>{agent_query}</user_query>" + + runtime_context = SurfSenseContextSchema( + search_space_id=search_space_id, + mentioned_document_ids=list( + resolved.mentioned_document_ids or (mentioned_document_ids or []) + ), + mentioned_folder_ids=list( + resolved.mentioned_folder_ids or (mentioned_folder_ids or []) + ), + mentioned_connector_ids=list(mentioned_connector_ids or []), + mentioned_connectors=connector_dicts, + ) + return agent_query, runtime_context + + async def run_agent_task( *, ctx: ActionContext, query: str, auto_approve_all: bool, + mentioned_document_ids: list[int] | None = None, + mentioned_folder_ids: list[int] | None = None, + mentioned_connector_ids: list[int] | None = None, + mentioned_connectors: list[MentionedDocumentInfo] | None = None, + mentioned_documents: list[MentionedDocumentInfo] | None = None, ) -> dict[str, Any]: """Invoke multi_agent_chat for one rendered query and return its outcome. Opens its own DB session so the executor's bookkeeping session isn't tied up for the entire invocation. The LangGraph ``thread_id`` (a fresh UUID) is returned as ``agent_session_id`` for later inspection. + + @-mentions (files / folders / connectors) chosen in the task input are + resolved the same way the chat flow does and forwarded to the agent via the + per-invocation ``context`` so they actually scope retrieval. """ agent_session_id = str(uuid.uuid4()) user_id = str(ctx.creator_user_id) if ctx.creator_user_id else None @@ -55,12 +160,24 @@ async def run_agent_task( agent_config=deps.agent_config, firecrawl_api_key=deps.firecrawl_api_key, thread_visibility=ChatVisibility.PRIVATE, + mentioned_document_ids=mentioned_document_ids, + ) + + agent_query, runtime_context = await _resolve_mention_context( + agent_session, + search_space_id=ctx.search_space_id, + query=query, + mentioned_document_ids=mentioned_document_ids, + mentioned_folder_ids=mentioned_folder_ids, + mentioned_connector_ids=mentioned_connector_ids, + mentioned_connectors=mentioned_connectors, + mentioned_documents=mentioned_documents, ) request_id = f"automation:{ctx.run_id}:{ctx.step_id}" turn_id = f"{request_id}:{int(time.time() * 1000)}" input_state: dict[str, Any] = { - "messages": [HumanMessage(content=query)], + "messages": [HumanMessage(content=agent_query)], "search_space_id": ctx.search_space_id, "request_id": request_id, "turn_id": turn_id, @@ -73,8 +190,17 @@ async def run_agent_task( }, "recursion_limit": 10_000, } + if runtime_context is not None: + runtime_context.request_id = request_id + runtime_context.turn_id = turn_id - result = await agent.ainvoke(input_state, config=config) + # The compiled graph declares ``context_schema=SurfSenseContextSchema``; + # mentions only reach ``KnowledgePriorityMiddleware`` via ``context=``. + invoke_kwargs: dict[str, Any] = {"config": config} + if runtime_context is not None: + invoke_kwargs["context"] = runtime_context + + result = await agent.ainvoke(input_state, **invoke_kwargs) resumes = 0 while True: @@ -87,7 +213,7 @@ async def run_agent_task( ) lg_resume_map, routed = build_auto_decisions(state, decision) config["configurable"]["surfsense_resume_value"] = routed - result = await agent.ainvoke(Command(resume=lg_resume_map), config=config) + result = await agent.ainvoke(Command(resume=lg_resume_map), **invoke_kwargs) resumes += 1 return { diff --git a/surfsense_backend/app/automations/actions/agent_task/params.py b/surfsense_backend/app/automations/actions/agent_task/params.py index b0e99a78b..ad6f35edb 100644 --- a/surfsense_backend/app/automations/actions/agent_task/params.py +++ b/surfsense_backend/app/automations/actions/agent_task/params.py @@ -4,6 +4,8 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field +from app.schemas.new_chat import MentionedDocumentInfo + class AgentTaskActionParams(BaseModel): """Run a multi_agent_chat turn from an automation step.""" @@ -19,3 +21,32 @@ class AgentTaskActionParams(BaseModel): default=False, description="If true, every HITL approval is auto-approved; otherwise rejected.", ) + + # @-mention references chosen in the task input. Mirror the ``new_chat`` + # request fields (minus SurfSense product docs) so the run can scope + # retrieval to the user's selected files / folders / connectors. All + # optional and additive; a task with no mentions behaves as before. + mentioned_document_ids: list[int] | None = Field( + default=None, + description="Knowledge-base document IDs the task references with @.", + ) + mentioned_folder_ids: list[int] | None = Field( + default=None, + description="Knowledge-base folder IDs the task references with @.", + ) + mentioned_connector_ids: list[int] | None = Field( + default=None, + description="Concrete connector account IDs the task references with @.", + ) + mentioned_connectors: list[MentionedDocumentInfo] | None = Field( + default=None, + description="Display/context metadata for the @-mentioned connector accounts.", + ) + mentioned_documents: list[MentionedDocumentInfo] | None = Field( + default=None, + description=( + "Chip metadata (id, title, kind, ...) for every @-mention so the " + "run can resolve titles to virtual paths and substitute them in " + "the query." + ), + ) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx index 6504af5a4..2c9db217d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx @@ -1,10 +1,11 @@ "use client"; import { ShieldAlert } from "lucide-react"; import { useAutomation } from "@/hooks/use-automation"; +import { AutomationBuilderForm } from "../../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../../hooks/use-automation-permissions"; import { AutomationDetailLoading } from "../components/automation-detail-loading"; import { AutomationNotFound } from "../components/automation-not-found"; -import { AutomationEditForm } from "./components/automation-edit-form"; +import { AutomationEditHeader } from "./components/automation-edit-header"; interface AutomationEditContentProps { searchSpaceId: number; @@ -49,5 +50,10 @@ export function AutomationEditContent({ searchSpaceId, automationId }: Automatio return <AutomationNotFound searchSpaceId={searchSpaceId} error={error} />; } - return <AutomationEditForm automation={automation} searchSpaceId={searchSpaceId} />; + return ( + <> + <AutomationEditHeader automation={automation} searchSpaceId={searchSpaceId} /> + <AutomationBuilderForm mode="edit" searchSpaceId={searchSpaceId} automation={automation} /> + </> + ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx deleted file mode 100644 index 9b950608e..000000000 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client"; -import { useAtomValue } from "jotai"; -import { AlertCircle, ArrowLeft, Save } from "lucide-react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; -import { JsonView } from "@/components/json-view"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Spinner } from "@/components/ui/spinner"; -import { type Automation, automationUpdateRequest } from "@/contracts/types/automation.types"; - -interface AutomationEditFormProps { - automation: Automation; - searchSpaceId: number; -} - -/** - * Edit-existing-automation form. Surfaces the four mutable fields - * (name, description, status, definition) as one editable JSON tree; - * triggers stay on the detail page where they have their own management - * UI. Validates with the same Zod schema the API expects, then PATCHes - * the changed shape back. - */ -export function AutomationEditForm({ automation, searchSpaceId }: AutomationEditFormProps) { - const router = useRouter(); - const { mutateAsync: updateAutomation, isPending } = useAtomValue(updateAutomationMutationAtom); - const detailHref = `/dashboard/${searchSpaceId}/automations/${automation.id}`; - - const [value, setValue] = useState(() => ({ - name: automation.name, - description: automation.description ?? null, - status: automation.status, - definition: automation.definition, - })); - const [issues, setIssues] = useState<string[]>([]); - - async function handleSave() { - setIssues([]); - const result = automationUpdateRequest.safeParse(value); - if (!result.success) { - setIssues( - result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) - ); - return; - } - try { - await updateAutomation({ automationId: automation.id, patch: result.data }); - router.push(detailHref); - } catch (err) { - setIssues([(err as Error).message ?? "Update failed"]); - } - } - - return ( - <> - <div className="space-y-3"> - <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> - <Link href={detailHref} className="text-xs text-muted-foreground"> - <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> - Back to automation - </Link> - </Button> - <div> - <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> - Edit automation - </h1> - <p className="text-sm text-muted-foreground mt-1">{automation.name}</p> - </div> - </div> - - <Card className="border-border/60 bg-accent"> - <CardHeader className="pb-4"> - <CardTitle className="text-base font-semibold">Definition</CardTitle> - </CardHeader> - <CardContent className="space-y-4"> - <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[36rem] overflow-auto"> - <JsonView - src={value} - editable - onChange={(next) => setValue(next as typeof value)} - collapsed={false} - /> - </div> - - {issues.length > 0 && ( - <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> - <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> - <AlertCircle className="h-3.5 w-3.5" aria-hidden /> - {issues.length === 1 ? "1 issue" : `${issues.length} issues`} - </div> - <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> - {issues.map((issue) => ( - <li key={issue}>{issue}</li> - ))} - </ul> - </div> - )} - - <div className="flex items-center justify-end gap-2"> - <Button asChild type="button" variant="ghost" size="sm"> - <Link href={detailHref}>Cancel</Link> - </Button> - <Button type="button" onClick={handleSave} disabled={isPending} size="sm"> - {isPending ? ( - <Spinner size="xs" className="mr-2" /> - ) : ( - <Save className="mr-2 h-4 w-4" /> - )} - Save changes - </Button> - </div> - </CardContent> - </Card> - </> - ); -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx new file mode 100644 index 000000000..6b2a31822 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx @@ -0,0 +1,31 @@ +"use client"; +import { ArrowLeft } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import type { Automation } from "@/contracts/types/automation.types"; + +interface AutomationEditHeaderProps { + automation: Automation; + searchSpaceId: number; +} + +export function AutomationEditHeader({ automation, searchSpaceId }: AutomationEditHeaderProps) { + const detailHref = `/dashboard/${searchSpaceId}/automations/${automation.id}`; + + return ( + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link href={detailHref} className="text-xs text-muted-foreground"> + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automation + </Link> + </Button> + <div> + <h1 className="text-xl md:text-2xl font-semibold text-foreground wrap-break-word"> + Edit automation + </h1> + <p className="text-sm text-muted-foreground mt-1">{automation.name}</p> + </div> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx new file mode 100644 index 000000000..740f199af --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx @@ -0,0 +1,129 @@ +"use client"; +import { useState } from "react"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import type { BuilderExecution } from "@/lib/automations/builder-schema"; +import { Field } from "./form-field"; + +interface AdvancedSectionProps { + execution: BuilderExecution; + tags: string[]; + onExecutionChange: (patch: Partial<BuilderExecution>) => void; + onTagsChange: (tags: string[]) => void; +} + +const BACKOFF_OPTIONS: ReadonlyArray<{ value: BuilderExecution["retryBackoff"]; label: string }> = [ + { value: "exponential", label: "Exponential" }, + { value: "linear", label: "Linear" }, + { value: "none", label: "None" }, +]; + +const CONCURRENCY_OPTIONS: ReadonlyArray<{ + value: BuilderExecution["concurrency"]; + label: string; +}> = [ + { value: "drop_if_running", label: "Skip if already running" }, + { value: "queue", label: "Queue the next run" }, + { value: "always", label: "Always run" }, +]; + +function clampInt(raw: string, min: number, fallback: number): number { + const value = Number.parseInt(raw, 10); + if (Number.isNaN(value)) return fallback; + return Math.max(min, value); +} + +export function AdvancedSection({ + execution, + tags, + onExecutionChange, + onTagsChange, +}: AdvancedSectionProps) { + const [tagsText, setTagsText] = useState(tags.join(", ")); + + function commitTags(text: string) { + const next = text + .split(",") + .map((tag) => tag.trim()) + .filter(Boolean); + onTagsChange(next); + } + + return ( + <div className="space-y-4"> + <div className="grid grid-cols-1 gap-3 sm:grid-cols-2"> + <Field label="Timeout (seconds)" hint="Wall-clock cap for the whole run."> + <Input + type="number" + min={1} + value={execution.timeoutSeconds} + onChange={(e) => + onExecutionChange({ timeoutSeconds: clampInt(e.target.value, 1, 600) }) + } + /> + </Field> + <Field label="Max retries" hint="Per-step retry budget."> + <Input + type="number" + min={0} + value={execution.maxRetries} + onChange={(e) => onExecutionChange({ maxRetries: clampInt(e.target.value, 0, 2) })} + /> + </Field> + <Field label="Retry backoff"> + <Select + value={execution.retryBackoff} + onValueChange={(value) => + onExecutionChange({ retryBackoff: value as BuilderExecution["retryBackoff"] }) + } + > + <SelectTrigger className="w-full"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {BACKOFF_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </Field> + <Field label="If already running"> + <Select + value={execution.concurrency} + onValueChange={(value) => + onExecutionChange({ concurrency: value as BuilderExecution["concurrency"] }) + } + > + <SelectTrigger className="w-full"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {CONCURRENCY_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </Field> + </div> + + <Field label="Tags" hint="Comma-separated. Optional."> + <Input + value={tagsText} + placeholder="research, weekly" + onChange={(e) => setTagsText(e.target.value)} + onBlur={(e) => commitTags(e.target.value)} + /> + </Field> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx new file mode 100644 index 000000000..1fd37cd3d --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx @@ -0,0 +1,459 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { Code2, LayoutList, Save } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useMemo, useState } from "react"; +import type { z } from "zod"; +import { + addTriggerMutationAtom, + createAutomationMutationAtom, + removeTriggerMutationAtom, + updateAutomationMutationAtom, + updateTriggerMutationAtom, +} from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { + type Automation, + automationCreateRequest, + automationUpdateRequest, +} from "@/contracts/types/automation.types"; +import { + type BuilderForm, + buildCreatePayload, + builderFormSchema, + buildScheduleTrigger, + buildUpdatePayload, + createEmptyForm, + formFromAutomation, + type HydratableTrigger, + hydrateForm, +} from "@/lib/automations/builder-schema"; +import { cn } from "@/lib/utils"; +import { AdvancedSection } from "./advanced-section"; +import { BasicsSection } from "./basics-section"; +import { BuilderSummary } from "./builder-summary"; +import { JsonModePanel } from "./json-mode-panel"; +import { ScheduleSection } from "./schedule-section"; +import { TaskList } from "./task-list"; +import { UnattendedToggle } from "./unattended-toggle"; + +interface AutomationBuilderFormProps { + mode: "create" | "edit"; + searchSpaceId: number; + /** Required in edit mode; seeds the form and trigger reconciliation. */ + automation?: Automation; +} + +type Mode = "form" | "json"; + +function mapFormErrors(error: z.ZodError): Record<string, string> { + const out: Record<string, string> = {}; + for (const issue of error.issues) { + const path = issue.path; + let key: string; + if (path[0] === "tasks" && typeof path[1] === "number") key = `tasks.${path[1]}.query`; + else if (path[0] === "schedule") key = "schedule"; + else key = String(path[0] ?? "_root"); + if (!out[key]) out[key] = issue.message; + } + return out; +} + +export function AutomationBuilderForm({ + mode, + searchSpaceId, + automation, +}: AutomationBuilderFormProps) { + const router = useRouter(); + const { mutateAsync: createAutomation } = useAtomValue(createAutomationMutationAtom); + const { mutateAsync: updateAutomation } = useAtomValue(updateAutomationMutationAtom); + const { mutateAsync: addTrigger } = useAtomValue(addTriggerMutationAtom); + const { mutateAsync: updateTrigger } = useAtomValue(updateTriggerMutationAtom); + const { mutateAsync: removeTrigger } = useAtomValue(removeTriggerMutationAtom); + + // Initial state: create starts empty in form mode; edit hydrates, falling + // back to JSON mode when the definition can't be represented in the form. + const initial = useMemo(() => { + if (mode === "edit" && automation) { + const result = formFromAutomation(automation); + if (result.formable) { + return { mode: "form" as Mode, form: result.form, notice: undefined }; + } + return { + mode: "json" as Mode, + form: createEmptyForm(), + notice: `This automation ${result.reason}, which the form can't show. Edit it as JSON below.`, + }; + } + return { mode: "form" as Mode, form: createEmptyForm(), notice: undefined }; + }, [mode, automation]); + + const [activeMode, setActiveMode] = useState<Mode>(initial.mode); + const [form, setForm] = useState<BuilderForm>(initial.form); + const [errors, setErrors] = useState<Record<string, string>>({}); + const [rootError, setRootError] = useState<string | null>(null); + + const [jsonValue, setJsonValue] = useState<Record<string, unknown>>(() => + initial.mode === "json" ? jsonFromAutomation(automation) : {} + ); + const [jsonIssues, setJsonIssues] = useState<string[]>([]); + const [jsonNotice, setJsonNotice] = useState<string | undefined>(initial.notice); + + const [submitting, setSubmitting] = useState(false); + + const cancelHref = + mode === "edit" && automation + ? `/dashboard/${searchSpaceId}/automations/${automation.id}` + : `/dashboard/${searchSpaceId}/automations`; + + function patchForm(patch: Partial<BuilderForm>) { + setForm((prev) => ({ ...prev, ...patch })); + } + + function jsonFromCurrentForm(): Record<string, unknown> { + if (mode === "edit" && automation) { + return { ...buildUpdatePayload(form), status: automation.status }; + } + const { search_space_id: _ignored, ...rest } = buildCreatePayload(form, searchSpaceId); + return rest; + } + + function switchToJson() { + setJsonValue(jsonFromCurrentForm()); + setJsonIssues([]); + setJsonNotice(undefined); + setActiveMode("json"); + } + + function switchToForm() { + const result = tryJsonToForm(); + if (result.ok) { + setForm(result.form); + setErrors({}); + setRootError(null); + setActiveMode("form"); + return; + } + setJsonIssues(result.issues); + setJsonNotice(result.notice); + } + + function tryJsonToForm(): + | { ok: true; form: BuilderForm } + | { ok: false; issues: string[]; notice?: string } { + // Read the raw tree defensively rather than strict-validating: an + // incomplete JSON edit should still round-trip into the form, where the + // form's own validation enforces completeness on submit. + const definition = jsonValue.definition; + if (!definition || typeof definition !== "object") { + return { ok: false, issues: [], notice: "Add a definition before switching to the form." }; + } + + const name = + typeof jsonValue.name === "string" + ? jsonValue.name + : mode === "edit" && automation + ? automation.name + : ""; + const description = typeof jsonValue.description === "string" ? jsonValue.description : null; + const triggers = + mode === "edit" && automation + ? (automation.triggers ?? []) + : extractTriggers(jsonValue.triggers); + + const h = hydrateForm(name, description, definition, triggers); + return h.formable + ? { ok: true, form: h.form } + : { ok: false, issues: [], notice: `Can't show in the form: it ${h.reason}.` }; + } + + function validateForm(): Record<string, string> | null { + const result = builderFormSchema.safeParse(form); + const next = result.success ? {} : mapFormErrors(result.error); + + // The schedule model fields aren't deeply validated by the schema. + if (form.schedule?.mode === "preset") { + const m = form.schedule.model; + if (m.frequency === "weekly" && m.daysOfWeek.length === 0) { + next.schedule = "Pick at least one day for the weekly schedule"; + } + } else if (form.schedule?.mode === "cron" && !form.schedule.cron.trim()) { + next.schedule = "Enter a schedule expression"; + } + + return Object.keys(next).length > 0 ? next : null; + } + + async function reconcileTriggers(automationId: number) { + const desired = buildScheduleTrigger(form); + const existing = (automation?.triggers ?? [])[0]; + if (!existing && desired) { + await addTrigger({ automationId, payload: desired }); + } else if (existing && !desired) { + await removeTrigger({ automationId, triggerId: existing.id }); + } else if (existing && desired) { + await updateTrigger({ + automationId, + triggerId: existing.id, + patch: { params: desired.params, enabled: desired.enabled }, + }); + } + } + + async function submitForm() { + setRootError(null); + const formErrors = validateForm(); + if (formErrors) { + setErrors(formErrors); + return; + } + setErrors({}); + + setSubmitting(true); + try { + if (mode === "edit" && automation) { + const payload = buildUpdatePayload(form); + const parsed = automationUpdateRequest.safeParse(payload); + if (!parsed.success) { + setRootError(zodIssueList(parsed.error).join("; ")); + return; + } + await updateAutomation({ automationId: automation.id, patch: parsed.data }); + await reconcileTriggers(automation.id); + router.push(`/dashboard/${searchSpaceId}/automations/${automation.id}`); + } else { + const payload = buildCreatePayload(form, searchSpaceId); + const parsed = automationCreateRequest.safeParse(payload); + if (!parsed.success) { + setRootError(zodIssueList(parsed.error).join("; ")); + return; + } + const created = await createAutomation(parsed.data); + router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); + } + } catch (err) { + setRootError((err as Error).message ?? "Submit failed"); + } finally { + setSubmitting(false); + } + } + + async function submitJson() { + setJsonIssues([]); + setSubmitting(true); + try { + if (mode === "edit" && automation) { + const parsed = automationUpdateRequest.safeParse(jsonValue); + if (!parsed.success) { + setJsonIssues(zodIssueList(parsed.error)); + return; + } + await updateAutomation({ automationId: automation.id, patch: parsed.data }); + router.push(`/dashboard/${searchSpaceId}/automations/${automation.id}`); + } else { + const parsed = automationCreateRequest.safeParse({ + ...jsonValue, + search_space_id: searchSpaceId, + }); + if (!parsed.success) { + setJsonIssues(zodIssueList(parsed.error)); + return; + } + const created = await createAutomation(parsed.data); + router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); + } + } catch (err) { + setJsonIssues([(err as Error).message ?? "Submit failed"]); + } finally { + setSubmitting(false); + } + } + + const submitLabel = mode === "edit" ? "Save changes" : "Create automation"; + + return ( + <div className="space-y-4"> + <div className="flex items-center justify-end"> + <div className="inline-flex rounded-md border border-border/60 p-0.5"> + <ModeButton + active={activeMode === "form"} + icon={LayoutList} + label="Form" + onClick={() => (activeMode === "form" ? undefined : switchToForm())} + /> + <ModeButton + active={activeMode === "json"} + icon={Code2} + label="Edit as JSON" + onClick={() => (activeMode === "json" ? undefined : switchToJson())} + /> + </div> + </div> + + {activeMode === "json" ? ( + <Card className="border-border/60 bg-accent"> + <CardContent className="pt-6"> + <JsonModePanel + value={jsonValue} + issues={jsonIssues} + notice={jsonNotice} + onChange={setJsonValue} + /> + </CardContent> + </Card> + ) : ( + <div className="grid grid-cols-1 gap-4 lg:grid-cols-3"> + <div className="space-y-4 lg:col-span-2"> + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Basics</CardTitle> + </CardHeader> + <CardContent> + <BasicsSection + name={form.name} + description={form.description} + errors={errors} + onChange={patchForm} + /> + </CardContent> + </Card> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Tasks</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <TaskList + tasks={form.tasks} + errors={errors} + searchSpaceId={searchSpaceId} + onChange={(tasks) => patchForm({ tasks })} + /> + <UnattendedToggle + checked={form.unattended} + onChange={(unattended) => patchForm({ unattended })} + /> + </CardContent> + </Card> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Schedule</CardTitle> + </CardHeader> + <CardContent> + <ScheduleSection + schedule={form.schedule} + timezone={form.timezone} + errors={errors} + onScheduleChange={(schedule) => patchForm({ schedule })} + onTimezoneChange={(timezone) => patchForm({ timezone })} + /> + </CardContent> + </Card> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Settings</CardTitle> + </CardHeader> + <CardContent> + <AdvancedSection + execution={form.execution} + tags={form.tags} + onExecutionChange={(patch) => + patchForm({ execution: { ...form.execution, ...patch } }) + } + onTagsChange={(tags) => patchForm({ tags })} + /> + </CardContent> + </Card> + </div> + + <div className="lg:col-span-1"> + <Card className="border-border/60 bg-accent lg:sticky lg:top-4"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Summary</CardTitle> + </CardHeader> + <CardContent> + <BuilderSummary form={form} /> + </CardContent> + </Card> + </div> + </div> + )} + + {rootError && <p className="text-right text-xs text-destructive">{rootError}</p>} + + <div className="flex items-center justify-end gap-2"> + <Button asChild type="button" variant="ghost" size="sm"> + <Link href={cancelHref}>Cancel</Link> + </Button> + <Button + type="button" + size="sm" + disabled={submitting} + onClick={() => (activeMode === "json" ? submitJson() : submitForm())} + > + {submitting ? <Spinner size="xs" className="mr-2" /> : <Save className="mr-2 h-4 w-4" />} + {submitLabel} + </Button> + </div> + </div> + ); +} + +function ModeButton({ + active, + icon: Icon, + label, + onClick, +}: { + active: boolean; + icon: typeof Code2; + label: string; + onClick: () => void; +}) { + return ( + <button + type="button" + onClick={onClick} + className={cn( + "inline-flex items-center gap-1.5 rounded-[5px] px-2.5 py-1 text-xs font-medium transition-colors", + active + ? "bg-background text-foreground shadow-sm" + : "text-muted-foreground hover:text-foreground" + )} + > + <Icon className="h-3.5 w-3.5" /> + {label} + </button> + ); +} + +function extractTriggers(raw: unknown): HydratableTrigger[] { + if (!Array.isArray(raw)) return []; + return raw.map((entry) => { + const obj = entry && typeof entry === "object" ? (entry as Record<string, unknown>) : {}; + return { + type: typeof obj.type === "string" ? obj.type : "", + params: + obj.params && typeof obj.params === "object" ? (obj.params as Record<string, unknown>) : {}, + }; + }); +} + +function zodIssueList(error: z.ZodError): string[] { + return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`); +} + +function jsonFromAutomation(automation: Automation | undefined): Record<string, unknown> { + if (!automation) return {}; + return { + name: automation.name, + description: automation.description ?? null, + status: automation.status, + definition: automation.definition, + }; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx new file mode 100644 index 000000000..fdc9f4526 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx @@ -0,0 +1,42 @@ +"use client"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Field } from "./form-field"; + +interface BasicsSectionProps { + name: string; + description: string | null; + errors: Record<string, string>; + onChange: (patch: { name?: string; description?: string | null }) => void; +} + +export function BasicsSection({ name, description, errors, onChange }: BasicsSectionProps) { + return ( + <div className="space-y-4"> + <Field label="Name" htmlFor="automation-name" required error={errors.name}> + <Input + id="automation-name" + value={name} + maxLength={200} + placeholder="Weekly competitor digest" + onChange={(e) => onChange({ name: e.target.value })} + /> + </Field> + + <Field + label="Description" + htmlFor="automation-description" + hint="Optional. A short note about what this automation is for." + error={errors.description} + > + <Textarea + id="automation-description" + value={description ?? ""} + rows={2} + placeholder="Summarize what changed and email me the highlights." + onChange={(e) => onChange({ description: e.target.value })} + /> + </Field> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx new file mode 100644 index 000000000..21a77cb5f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx @@ -0,0 +1,96 @@ +"use client"; +import { CalendarClock, CheckCircle2, ListOrdered, type LucideIcon, XCircle } from "lucide-react"; +import { type BuilderForm, scheduleToCron } from "@/lib/automations/builder-schema"; +import { describeCron } from "@/lib/automations/describe-cron"; + +interface BuilderSummaryProps { + form: BuilderForm; +} + +/** + * Live, read-only mirror of what will be created. Mirrors the layout of the + * chat ``AutomationDraftPreview`` so the two creation paths feel consistent. + */ +export function BuilderSummary({ form }: BuilderSummaryProps) { + const scheduleLabel = form.schedule + ? `${describeCron(scheduleToCron(form.schedule))} · ${form.timezone}` + : "No schedule — won't run automatically"; + + return ( + <div className="space-y-4 text-sm"> + <div className="space-y-1"> + <p className="font-medium text-foreground">{form.name.trim() || "Untitled automation"}</p> + {form.description?.trim() && ( + <p className="text-xs text-muted-foreground">{form.description.trim()}</p> + )} + </div> + + <Section icon={CalendarClock} label="Schedule"> + <p className="text-xs text-foreground">{scheduleLabel}</p> + </Section> + + <Section + icon={ListOrdered} + label={`Tasks · ${form.tasks.length} step${form.tasks.length === 1 ? "" : "s"}`} + > + <ol className="space-y-1.5 text-xs"> + {form.tasks.map((task, index) => ( + <li key={task.id} className="flex items-start gap-2"> + <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground shrink-0 mt-0.5"> + {index + 1} + </span> + <span className="min-w-0 flex-1 space-y-1"> + <span className="block text-foreground line-clamp-2"> + {task.query.trim() || ( + <span className="text-muted-foreground">No instructions yet</span> + )} + </span> + {task.mentions.length > 0 && ( + <span className="flex flex-wrap gap-1"> + {task.mentions.map((mention) => ( + <span + key={`${mention.kind}:${mention.id}`} + className="inline-flex max-w-[140px] items-center truncate rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary/70" + > + @{mention.title} + </span> + ))} + </span> + )} + </span> + </li> + ))} + </ol> + </Section> + + <div className="flex items-center gap-1.5 text-xs text-muted-foreground"> + {form.unattended ? ( + <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" aria-hidden /> + ) : ( + <XCircle className="h-3.5 w-3.5" aria-hidden /> + )} + {form.unattended ? "Runs without approval prompts" : "Will reject approval prompts"} + </div> + </div> + ); +} + +function Section({ + icon: Icon, + label, + children, +}: { + icon: LucideIcon; + label: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-1.5"> + <div className="flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground uppercase tracking-wider"> + <Icon className="h-3 w-3" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx new file mode 100644 index 000000000..222efd9c6 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx @@ -0,0 +1,42 @@ +"use client"; +import { AlertCircle } from "lucide-react"; +import type { ReactNode } from "react"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; + +interface FieldProps { + label?: string; + htmlFor?: string; + hint?: string; + error?: string; + required?: boolean; + className?: string; + children: ReactNode; +} + +/** + * Label + control + (hint | inline error) stack shared by every builder + * section. Keeps spacing and error styling consistent so individual sections + * stay focused on their inputs. + */ +export function Field({ label, htmlFor, hint, error, required, className, children }: FieldProps) { + return ( + <div className={cn("space-y-1.5", className)}> + {label && ( + <Label htmlFor={htmlFor} className="text-xs font-medium text-foreground"> + {label} + {required && <span className="text-muted-foreground">*</span>} + </Label> + )} + {children} + {error ? ( + <p className="flex items-center gap-1 text-xs text-destructive"> + <AlertCircle className="h-3 w-3 shrink-0" aria-hidden /> + {error} + </p> + ) : hint ? ( + <p className="text-xs text-muted-foreground">{hint}</p> + ) : null} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx new file mode 100644 index 000000000..1f25f8a61 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx @@ -0,0 +1,51 @@ +"use client"; +import { AlertCircle } from "lucide-react"; +import { JsonView } from "@/components/json-view"; + +interface JsonModePanelProps { + value: Record<string, unknown>; + issues: string[]; + notice?: string; + onChange: (next: Record<string, unknown>) => void; +} + +/** + * Raw-JSON escape hatch. Edits the same payload the form produces; the + * orchestrator validates it against the contract schema on submit. Shown when + * the user opts into "Edit as JSON" or when an existing definition uses + * features the form can't represent. + */ +export function JsonModePanel({ value, issues, notice, onChange }: JsonModePanelProps) { + return ( + <div className="space-y-4"> + {notice && ( + <div className="rounded-md border border-amber-500/40 bg-amber-500/5 px-3 py-2 text-xs text-amber-700 dark:text-amber-400"> + {notice} + </div> + )} + + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-144 overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => onChange(next as Record<string, unknown>)} + collapsed={false} + /> + </div> + + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx new file mode 100644 index 000000000..312454056 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; +import { + InlineMentionEditor, + type InlineMentionEditorRef, + type MentionChipInput, + type MentionedDocument, + type SuggestionAnchorRect, + type SuggestionTriggerInfo, +} from "@/components/assistant-ui/inline-mention-editor"; +import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; +import { + DocumentMentionPicker, + type DocumentMentionPickerRef, +} from "@/components/new-chat/document-mention-picker"; +import { Popover, PopoverAnchor } from "@/components/ui/popover"; +import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; +import { cn } from "@/lib/utils"; + +interface MentionTaskInputProps { + searchSpaceId: number; + value: string; + mentions: MentionedDocumentInfo[]; + onChange: (text: string, mentions: MentionedDocumentInfo[]) => void; + placeholder?: string; + disabled?: boolean; +} + +type AnchorPoint = { left: number; top: number }; + +// Mirror of thread.tsx's getComposerSuggestionAnchorPoint -- kept local so the +// chat composer stays untouched. +function getAnchorPoint(rect: SuggestionAnchorRect | null): AnchorPoint | null { + if (!rect) return null; + return { left: rect.left, top: rect.bottom }; +} + +/** Project the editor's chip shape into the canonical mention info union. */ +function toMentionInfo(doc: MentionedDocument): MentionedDocumentInfo { + if (doc.kind === "connector") { + return { + id: doc.id, + title: doc.title, + kind: "connector", + connector_type: doc.connector_type ?? "UNKNOWN", + account_name: doc.account_name ?? doc.title, + }; + } + if (doc.kind === "folder") { + return { id: doc.id, title: doc.title, kind: "folder" }; + } + return { + id: doc.id, + title: doc.title, + document_type: doc.document_type ?? "UNKNOWN", + kind: "doc", + }; +} + +/** Project a mention info into the editor's chip-insertion shape. */ +function toChipInput(mention: MentionedDocumentInfo): MentionChipInput { + if (mention.kind === "connector") { + return { + id: mention.id, + title: mention.title, + kind: "connector", + connector_type: mention.connector_type, + account_name: mention.account_name, + }; + } + if (mention.kind === "folder") { + return { id: mention.id, title: mention.title, kind: "folder" }; + } + return { + id: mention.id, + title: mention.title, + kind: "doc", + document_type: mention.document_type, + }; +} + +function removeFirstToken(text: string, token: string): string { + const index = text.indexOf(token); + if (index === -1) return text; + return text.slice(0, index) + text.slice(index + token.length); +} + +/** + * Task input that reuses the chat ``@`` mention experience -- the same + * ``InlineMentionEditor`` + ``DocumentMentionPicker`` as the composer, minus + * SurfSense product docs. The editor is the source of truth while mounted; + * ``onChange`` reports both the plain text (chips rendered as ``@Title``) and + * the structured mention list so the builder can persist IDs for the run. + */ +export function MentionTaskInput({ + searchSpaceId, + value, + mentions, + onChange, + placeholder, + disabled, +}: MentionTaskInputProps) { + const editorRef = useRef<InlineMentionEditorRef>(null); + const pickerRef = useRef<DocumentMentionPickerRef>(null); + + const [showPopover, setShowPopover] = useState(false); + const [mentionQuery, setMentionQuery] = useState(""); + const [anchorPoint, setAnchorPoint] = useState<AnchorPoint | null>(null); + + // One-shot hydration of existing mentions into real chips. ``initialText`` + // seeds the literal ``@Title`` text; here we strip those tokens and + // re-insert them as chips so the editor reports the structured docs (and + // editing can't silently drop the mention IDs). Position isn't preserved + // on re-hydration -- chips append after the remaining prose. + const didHydrateRef = useRef(false); + useEffect(() => { + if (didHydrateRef.current) return; + didHydrateRef.current = true; + if (mentions.length === 0) return; + const editor = editorRef.current; + if (!editor) return; + + let baseText = value; + for (const mention of mentions) { + baseText = removeFirstToken(baseText, `@${mention.title}`); + } + baseText = baseText.replace(/[ \t]{2,}/g, " ").trim(); + editor.setText(baseText); + for (const mention of mentions) { + editor.insertMentionChip(toChipInput(mention), { removeTriggerText: false }); + } + }, [mentions, value]); + + const closePopover = useCallback(() => { + setShowPopover(false); + setMentionQuery(""); + setAnchorPoint(null); + }, []); + + const handleEditorChange = useCallback( + (text: string, docs: MentionedDocument[]) => { + onChange(text, docs.map(toMentionInfo)); + }, + [onChange] + ); + + const handleMentionTrigger = useCallback((trigger: SuggestionTriggerInfo) => { + const point = getAnchorPoint(trigger.anchorRect); + if (!point) { + setShowPopover(false); + setMentionQuery(""); + setAnchorPoint(null); + return; + } + setAnchorPoint((current) => current ?? point); + setShowPopover(true); + setMentionQuery(trigger.query); + }, []); + + const handleMentionClose = useCallback(() => { + setShowPopover((open) => { + if (open) { + setMentionQuery(""); + setAnchorPoint(null); + } + return false; + }); + }, []); + + const handlePopoverOpenChange = useCallback((open: boolean) => { + setShowPopover(open); + if (!open) { + setMentionQuery(""); + setAnchorPoint(null); + } + }, []); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (!showPopover) return; + if (e.key === "ArrowDown") { + e.preventDefault(); + pickerRef.current?.moveDown(); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + pickerRef.current?.moveUp(); + } else if (e.key === "Enter") { + e.preventDefault(); + pickerRef.current?.selectHighlighted(); + } else if (e.key === "Escape") { + e.preventDefault(); + if (pickerRef.current?.goBack()) return; + closePopover(); + } + }, + [showPopover, closePopover] + ); + + const handleSelection = useCallback( + (picked: MentionedDocumentInfo[]) => { + const editor = editorRef.current; + const existing = new Set( + (editor?.getMentionedDocuments() ?? []).map((doc) => getMentionDocKey(doc)) + ); + for (const mention of picked) { + const key = getMentionDocKey(mention); + if (existing.has(key)) continue; + editor?.insertMentionChip(toChipInput(mention)); + existing.add(key); + } + closePopover(); + }, + [closePopover] + ); + + return ( + <div + className={cn( + "border-popover-border focus-within:border-ring focus-within:ring-ring/50 dark:bg-input/30 min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]", + disabled && "cursor-not-allowed opacity-50" + )} + > + <Popover open={showPopover} onOpenChange={handlePopoverOpenChange}> + {anchorPoint ? ( + <> + <PopoverAnchor + className="pointer-events-none fixed size-0" + style={{ left: anchorPoint.left, top: anchorPoint.top }} + /> + <ComposerSuggestionPopoverContent side="bottom"> + <DocumentMentionPicker + ref={pickerRef} + searchSpaceId={searchSpaceId} + includeSurfsenseDocs={false} + onSelectionChange={handleSelection} + onDone={closePopover} + initialSelectedDocuments={mentions} + externalSearch={mentionQuery} + /> + </ComposerSuggestionPopoverContent> + </> + ) : null} + </Popover> + <InlineMentionEditor + ref={editorRef} + initialText={value} + placeholder={placeholder ?? "Type @ to reference files, folders, or connectors"} + disabled={disabled} + onChange={handleEditorChange} + onMentionTrigger={handleMentionTrigger} + onMentionClose={handleMentionClose} + onKeyDown={handleKeyDown} + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx new file mode 100644 index 000000000..401b4f5cb --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx @@ -0,0 +1,275 @@ +"use client"; +import { CalendarClock, CalendarOff, Plus, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { type BuilderSchedule, scheduleToCron } from "@/lib/automations/builder-schema"; +import { describeCron } from "@/lib/automations/describe-cron"; +import { + DEFAULT_SCHEDULE, + FREQUENCY_OPTIONS, + fromCron, + type ScheduleFrequency, + type ScheduleModel, + toCron, + WEEKDAY_OPTIONS, +} from "@/lib/automations/schedule-builder"; +import { cn } from "@/lib/utils"; +import { Field } from "./form-field"; +import { TimezoneCombobox } from "./timezone-combobox"; + +interface ScheduleSectionProps { + schedule: BuilderSchedule | null; + timezone: string; + errors: Record<string, string>; + onScheduleChange: (schedule: BuilderSchedule | null) => void; + onTimezoneChange: (timezone: string) => void; +} + +function pad(value: number): string { + return value.toString().padStart(2, "0"); +} + +export function ScheduleSection({ + schedule, + timezone, + errors, + onScheduleChange, + onTimezoneChange, +}: ScheduleSectionProps) { + if (schedule === null) { + return ( + <div className="rounded-lg border border-dashed border-border/60 bg-muted/20 px-4 py-6 text-center"> + <CalendarOff className="mx-auto h-7 w-7 text-muted-foreground" aria-hidden /> + <p className="mt-2 text-sm text-foreground">No schedule</p> + <p className="mt-0.5 text-xs text-muted-foreground"> + This automation won't run automatically until you add one. + </p> + <Button + type="button" + variant="outline" + size="sm" + className="mt-3" + onClick={() => onScheduleChange({ mode: "preset", model: { ...DEFAULT_SCHEDULE } })} + > + <Plus className="mr-1.5 h-4 w-4" /> + Add a schedule + </Button> + </div> + ); + } + + const cron = scheduleToCron(schedule); + const label = describeCron(cron); + + return ( + <div className="space-y-3"> + <div className="flex items-start justify-between gap-3 rounded-md border border-border/60 bg-background px-3 py-2"> + <div className="flex items-center gap-2 text-sm min-w-0"> + <CalendarClock className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden /> + <span className="font-medium text-foreground truncate">{label}</span> + <span className="text-muted-foreground shrink-0">· {timezone}</span> + </div> + <Button + type="button" + variant="ghost" + size="icon" + className="h-6 w-6 shrink-0 text-muted-foreground hover:text-destructive" + aria-label="Remove schedule" + onClick={() => onScheduleChange(null)} + > + <X className="h-4 w-4" /> + </Button> + </div> + + {schedule.mode === "preset" ? ( + <PresetEditor + model={schedule.model} + onChange={(model) => onScheduleChange({ mode: "preset", model })} + onSwitchToCron={() => onScheduleChange({ mode: "cron", cron: toCron(schedule.model) })} + /> + ) : ( + <CronEditor + cron={schedule.cron} + error={errors.schedule} + onChange={(value) => onScheduleChange({ mode: "cron", cron: value })} + onSwitchToPreset={() => + onScheduleChange({ + mode: "preset", + model: fromCron(schedule.cron) ?? { ...DEFAULT_SCHEDULE }, + }) + } + /> + )} + + <Field label="Timezone"> + <TimezoneCombobox value={timezone} onChange={onTimezoneChange} /> + </Field> + </div> + ); +} + +interface PresetEditorProps { + model: ScheduleModel; + onChange: (model: ScheduleModel) => void; + onSwitchToCron: () => void; +} + +function PresetEditor({ model, onChange, onSwitchToCron }: PresetEditorProps) { + const weeklyNoDays = model.frequency === "weekly" && model.daysOfWeek.length === 0; + + return ( + <div className="space-y-3"> + <div className="grid grid-cols-1 gap-3 sm:grid-cols-2"> + <Field label="Frequency"> + <Select + value={model.frequency} + onValueChange={(value) => onChange({ ...model, frequency: value as ScheduleFrequency })} + > + <SelectTrigger className="w-full"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {FREQUENCY_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </Field> + + {model.frequency === "hourly" ? ( + <Field label="At minute"> + <Input + type="number" + min={0} + max={59} + value={model.minute} + onChange={(e) => onChange({ ...model, minute: clampInt(e.target.value, 0, 59) })} + /> + </Field> + ) : ( + <Field label="At time"> + <Input + type="time" + value={`${pad(model.hour)}:${pad(model.minute)}`} + onChange={(e) => { + const [h, m] = e.target.value.split(":"); + onChange({ + ...model, + hour: clampInt(h, 0, 23), + minute: clampInt(m, 0, 59), + }); + }} + /> + </Field> + )} + </div> + + {model.frequency === "weekly" && ( + <Field label="On days" error={weeklyNoDays ? "Pick at least one day" : undefined}> + <div className="flex flex-wrap gap-1.5"> + {WEEKDAY_OPTIONS.map((day) => { + const active = model.daysOfWeek.includes(day.value); + return ( + <button + key={day.value} + type="button" + aria-pressed={active} + onClick={() => + onChange({ ...model, daysOfWeek: toggleDay(model.daysOfWeek, day.value) }) + } + className={cn( + "rounded-md border px-2.5 py-1 text-xs font-medium transition-colors", + active + ? "border-primary bg-primary text-primary-foreground" + : "border-border/60 bg-background text-muted-foreground hover:bg-muted" + )} + > + {day.short} + </button> + ); + })} + </div> + </Field> + )} + + {model.frequency === "monthly" && ( + <Field label="Day of month" hint={"1\u201331."}> + <Input + type="number" + min={1} + max={31} + value={model.dayOfMonth} + onChange={(e) => onChange({ ...model, dayOfMonth: clampInt(e.target.value, 1, 31) })} + className="w-24" + /> + </Field> + )} + + <button + type="button" + onClick={onSwitchToCron} + className="text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" + > + Advanced: enter a schedule expression + </button> + </div> + ); +} + +interface CronEditorProps { + cron: string; + error?: string; + onChange: (cron: string) => void; + onSwitchToPreset: () => void; +} + +function CronEditor({ cron, error, onChange, onSwitchToPreset }: CronEditorProps) { + const trimmed = cron.trim(); + const label = trimmed ? describeCron(trimmed) : null; + + return ( + <div className="space-y-2"> + <Field + label="Schedule expression" + hint="Five-field cron, e.g. 0 9 * * 1-5 (minute hour day month weekday)." + error={error} + > + <Input + value={cron} + placeholder="0 9 * * 1-5" + className="font-mono" + onChange={(e) => onChange(e.target.value)} + /> + </Field> + {label && label !== trimmed && <p className="text-xs text-muted-foreground">Runs: {label}</p>} + <button + type="button" + onClick={onSwitchToPreset} + className="text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" + > + Use the simple picker + </button> + </div> + ); +} + +function clampInt(raw: string, min: number, max: number): number { + const value = Number.parseInt(raw, 10); + if (Number.isNaN(value)) return min; + return Math.min(max, Math.max(min, value)); +} + +function toggleDay(days: number[], value: number): number[] { + return days.includes(value) + ? days.filter((day) => day !== value) + : [...days, value].sort((a, b) => a - b); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx new file mode 100644 index 000000000..55b9ea406 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx @@ -0,0 +1,136 @@ +"use client"; +import { ChevronDown, ChevronUp, Trash2 } from "lucide-react"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import type { BuilderTask } from "@/lib/automations/builder-schema"; +import { Field } from "./form-field"; +import { MentionTaskInput } from "./mention-task-input"; + +interface TaskItemProps { + index: number; + total: number; + task: BuilderTask; + searchSpaceId: number; + error?: string; + onChange: (patch: Partial<BuilderTask>) => void; + onMoveUp: () => void; + onMoveDown: () => void; + onRemove: () => void; +} + +function parseOptionalInt(raw: string): number | null { + const trimmed = raw.trim(); + if (trimmed === "") return null; + const value = Number.parseInt(trimmed, 10); + return Number.isNaN(value) ? null : value; +} + +export function TaskItem({ + index, + total, + task, + searchSpaceId, + error, + onChange, + onMoveUp, + onMoveDown, + onRemove, +}: TaskItemProps) { + return ( + <div className="rounded-lg border border-border/60 bg-background p-3 space-y-3"> + <div className="flex items-center justify-between gap-2"> + <span className="inline-flex items-center gap-2 text-xs font-medium text-muted-foreground"> + <span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-foreground"> + {index + 1} + </span> + Task {index + 1} + </span> + <div className="flex items-center gap-0.5"> + <Button + type="button" + variant="ghost" + size="icon" + className="h-7 w-7 text-muted-foreground" + disabled={index === 0} + aria-label="Move task up" + onClick={onMoveUp} + > + <ChevronUp className="h-4 w-4" /> + </Button> + <Button + type="button" + variant="ghost" + size="icon" + className="h-7 w-7 text-muted-foreground" + disabled={index === total - 1} + aria-label="Move task down" + onClick={onMoveDown} + > + <ChevronDown className="h-4 w-4" /> + </Button> + <Button + type="button" + variant="ghost" + size="icon" + className="h-7 w-7 text-muted-foreground hover:text-destructive" + disabled={total === 1} + aria-label="Remove task" + onClick={onRemove} + > + <Trash2 className="h-4 w-4" /> + </Button> + </div> + </div> + + <Field + error={error} + hint="Type @ to reference files, folders, or connectors for extra context." + > + <MentionTaskInput + searchSpaceId={searchSpaceId} + value={task.query} + mentions={task.mentions} + placeholder="What should the agent do? e.g. Summarize new docs in @Marketing since the last run." + onChange={(query, mentions) => onChange({ query, mentions })} + /> + </Field> + + <Accordion type="single" collapsible> + <AccordionItem value="advanced" className="border-b-0"> + <AccordionTrigger className="py-1.5 text-xs text-muted-foreground hover:no-underline"> + Advanced + </AccordionTrigger> + <AccordionContent className="pb-1"> + <div className="grid grid-cols-2 gap-3"> + <Field label="Max retries" hint="Leave blank to use the default."> + <Input + type="number" + min={0} + max={10} + value={task.maxRetries ?? ""} + placeholder="default" + onChange={(e) => onChange({ maxRetries: parseOptionalInt(e.target.value) })} + /> + </Field> + <Field label="Timeout (seconds)" hint="Leave blank to use the default."> + <Input + type="number" + min={1} + value={task.timeoutSeconds ?? ""} + placeholder="default" + onChange={(e) => onChange({ timeoutSeconds: parseOptionalInt(e.target.value) })} + /> + </Field> + </div> + </AccordionContent> + </AccordionItem> + </Accordion> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx new file mode 100644 index 000000000..41a53542f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx @@ -0,0 +1,65 @@ +"use client"; +import { Plus } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { type BuilderTask, emptyTask } from "@/lib/automations/builder-schema"; +import { TaskItem } from "./task-item"; + +interface TaskListProps { + tasks: BuilderTask[]; + errors: Record<string, string>; + searchSpaceId: number; + onChange: (tasks: BuilderTask[]) => void; +} + +/** + * Ordered list of agent tasks. Steps run sequentially in the order shown. + * Reordering is done with up/down buttons to avoid a drag-and-drop dependency. + */ +export function TaskList({ tasks, errors, searchSpaceId, onChange }: TaskListProps) { + function updateAt(index: number, patch: Partial<BuilderTask>) { + onChange(tasks.map((task, i) => (i === index ? { ...task, ...patch } : task))); + } + + function removeAt(index: number) { + onChange(tasks.filter((_, i) => i !== index)); + } + + function move(index: number, direction: -1 | 1) { + const target = index + direction; + if (target < 0 || target >= tasks.length) return; + const next = [...tasks]; + [next[index], next[target]] = [next[target], next[index]]; + onChange(next); + } + + return ( + <div className="space-y-3"> + {tasks.map((task, index) => ( + <TaskItem + key={task.id} + index={index} + total={tasks.length} + task={task} + searchSpaceId={searchSpaceId} + error={errors[`tasks.${index}.query`]} + onChange={(patch) => updateAt(index, patch)} + onMoveUp={() => move(index, -1)} + onMoveDown={() => move(index, 1)} + onRemove={() => removeAt(index)} + /> + ))} + + {errors.tasks && <p className="text-xs text-destructive">{errors.tasks}</p>} + + <Button + type="button" + variant="outline" + size="sm" + onClick={() => onChange([...tasks, emptyTask()])} + > + <Plus className="mr-1.5 h-4 w-4" /> + Add task + </Button> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx new file mode 100644 index 000000000..bc3b97542 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx @@ -0,0 +1,71 @@ +"use client"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { getTimezones } from "@/lib/automations/builder-schema"; +import { cn } from "@/lib/utils"; + +interface TimezoneComboboxProps { + value: string; + onChange: (value: string) => void; +} + +/** + * Searchable IANA timezone picker. The full ``Intl.supportedValuesOf`` list is + * long, so it lives behind a Command search instead of a flat Select. + */ +export function TimezoneCombobox({ value, onChange }: TimezoneComboboxProps) { + const [open, setOpen] = useState(false); + const timezones = useMemo(() => getTimezones(), []); + + return ( + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + type="button" + variant="outline" + role="combobox" + aria-expanded={open} + className="w-full justify-between font-normal" + > + <span className="truncate">{value || "Select timezone"}</span> + <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + </Button> + </PopoverTrigger> + <PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start"> + <Command> + <CommandInput placeholder="Search timezone..." /> + <CommandList> + <CommandEmpty>No timezone found.</CommandEmpty> + <CommandGroup> + {timezones.map((tz) => ( + <CommandItem + key={tz} + value={tz} + onSelect={() => { + onChange(tz); + setOpen(false); + }} + > + <Check + className={cn("mr-2 h-4 w-4", value === tz ? "opacity-100" : "opacity-0")} + /> + {tz} + </CommandItem> + ))} + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx new file mode 100644 index 000000000..ba665445f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx @@ -0,0 +1,47 @@ +"use client"; +import { Info } from "lucide-react"; +import { Switch } from "@/components/ui/switch"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +interface UnattendedToggleProps { + checked: boolean; + onChange: (checked: boolean) => void; +} + +/** + * Maps to ``auto_approve_all`` on every agent task. Automations run with no one + * watching, so this defaults ON; turning it off means any approval prompt the + * agent raises is rejected and the step can stall. + */ +export function UnattendedToggle({ checked, onChange }: UnattendedToggleProps) { + return ( + <div className="flex items-start justify-between gap-3 rounded-lg border border-border/60 bg-background px-3 py-3"> + <div className="space-y-0.5 min-w-0"> + <div className="flex items-center gap-1.5"> + <span className="text-sm font-medium text-foreground"> + Run without asking for approvals + </span> + <Tooltip> + <TooltipTrigger asChild> + <button type="button" aria-label="More info" className="text-muted-foreground"> + <Info className="h-3.5 w-3.5" /> + </button> + </TooltipTrigger> + <TooltipContent className="max-w-xs"> + Automations run unattended. With this off, any approval the agent asks for is + rejected, which can stall a step. + </TooltipContent> + </Tooltip> + </div> + <p className="text-xs text-muted-foreground"> + Auto-approve actions the agent would normally pause to confirm. + </p> + </div> + <Switch + checked={checked} + onCheckedChange={onChange} + aria-label="Run without asking for approvals" + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx index f03b3f4c8..0c983aaf8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -1,7 +1,7 @@ "use client"; import { ShieldAlert } from "lucide-react"; +import { AutomationBuilderForm } from "../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../hooks/use-automation-permissions"; -import { AutomationJsonForm } from "./components/automation-json-form"; import { AutomationNewHeader } from "./components/automation-new-header"; interface AutomationNewContentProps { @@ -9,10 +9,10 @@ interface AutomationNewContentProps { } /** - * Orchestrator for the raw-JSON create route. Gates on - * ``automations:create`` so users who can't create don't even see the - * form; same panel as the detail page's access-denied state for - * consistency. + * Orchestrator for the create route. Gates on ``automations:create`` so users + * who can't create don't even see the form; same panel as the detail page's + * access-denied state for consistency. The builder defaults to the friendly + * form with a raw-JSON escape hatch. */ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { const perms = useAutomationPermissions(); @@ -36,7 +36,7 @@ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProp return ( <> <AutomationNewHeader searchSpaceId={searchSpaceId} /> - <AutomationJsonForm searchSpaceId={searchSpaceId} /> + <AutomationBuilderForm mode="create" searchSpaceId={searchSpaceId} /> </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx deleted file mode 100644 index 94b608b8f..000000000 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; -import { useAtomValue } from "jotai"; -import { AlertCircle, FileJson, Save } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { createAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; -import { JsonView } from "@/components/json-view"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Spinner } from "@/components/ui/spinner"; -import { automationCreateRequest } from "@/contracts/types/automation.types"; -import { DEFAULT_AUTOMATION_TEMPLATE } from "@/lib/automations/default-template"; - -interface AutomationJsonFormProps { - searchSpaceId: number; -} - -/** - * Raw-JSON create form. Lets power users skip the chat drafter when they - * already know the shape they want. Flow: - * edit tree → inject search_space_id → Zod validate → POST → navigate - * - * ``search_space_id`` is injected here rather than required in the edited - * tree — the user shouldn't have to know their numeric id, and it keeps - * the template copy-paste-friendly across search spaces. - */ -export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { - const router = useRouter(); - const { mutateAsync: createAutomation, isPending } = useAtomValue(createAutomationMutationAtom); - const [value, setValue] = useState<Record<string, unknown>>( - () => DEFAULT_AUTOMATION_TEMPLATE as Record<string, unknown> - ); - const [issues, setIssues] = useState<string[]>([]); - - async function handleSubmit() { - setIssues([]); - - const payload = { ...value, search_space_id: searchSpaceId }; - const result = automationCreateRequest.safeParse(payload); - if (!result.success) { - setIssues( - result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) - ); - return; - } - - try { - const created = await createAutomation(result.data); - router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); - } catch (err) { - setIssues([(err as Error).message ?? "Submit failed"]); - } - } - - const hasIssues = issues.length > 0; - - return ( - <Card className="border-border/60 bg-accent"> - <CardHeader className="pb-4"> - <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> - <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> - Definition + triggers - </CardTitle> - </CardHeader> - <CardContent className="space-y-4"> - <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[32rem] overflow-auto"> - <JsonView - src={value} - editable - onChange={(next) => setValue(next as Record<string, unknown>)} - collapsed={false} - /> - </div> - - {hasIssues && ( - <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> - <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> - <AlertCircle className="h-3.5 w-3.5" aria-hidden /> - {issues.length === 1 ? "1 issue" : `${issues.length} issues`} - </div> - <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> - {issues.map((issue) => ( - <li key={issue}>{issue}</li> - ))} - </ul> - </div> - )} - - <div className="flex items-center justify-end gap-2"> - <Button type="button" onClick={handleSubmit} disabled={isPending} size="sm"> - {isPending ? <Spinner size="xs" className="mr-2" /> : <Save className="mr-2 h-4 w-4" />} - Create automation - </Button> - </div> - </CardContent> - </Card> - ); -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx index aef2744d5..ccfbbc9fa 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx @@ -22,12 +22,9 @@ export function AutomationNewHeader({ searchSpaceId }: AutomationNewHeaderProps) <div className="flex items-start justify-between gap-4 flex-wrap"> <div className="space-y-1"> - <h1 className="text-xl md:text-2xl font-semibold text-foreground"> - New automation · raw JSON - </h1> + <h1 className="text-xl md:text-2xl font-semibold text-foreground">New automation</h1> <p className="text-sm text-muted-foreground max-w-2xl"> - Paste an ``AutomationCreate`` payload and submit. Validated against the schema before - save. Prefer natural language? Use chat instead. + Set up a task and a schedule. Prefer natural language? Use chat instead. </p> </div> <Button asChild variant="outline" size="sm"> diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c2a0f1665..8e3fd4ca8 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -57,6 +57,13 @@ interface DocumentMentionPickerProps { onDone: () => void; initialSelectedDocuments?: MentionedDocumentInfo[]; externalSearch?: string; + /** + * Whether to surface the "SurfSense Docs" (product documentation) branch + * and include those docs in search results. Defaults to ``true`` so the + * chat composer is unchanged; callers like the automation task input pass + * ``false`` to reference only the user's own knowledge base + connectors. + */ + includeSurfsenseDocs?: boolean; } const PAGE_SIZE = 20; @@ -228,7 +235,14 @@ export const DocumentMentionPicker = forwardRef< DocumentMentionPickerRef, DocumentMentionPickerProps >(function DocumentMentionPicker( - { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "" }, + { + searchSpaceId, + onSelectionChange, + onDone, + initialSelectedDocuments = [], + externalSearch = "", + includeSurfsenseDocs = true, + }, ref ) { const search = externalSearch; @@ -307,7 +321,7 @@ export const DocumentMentionPicker = forwardRef< queryFn: ({ signal }) => documentsApiService.getSurfsenseDocs({ queryParams: surfsenseDocsQueryParams }, signal), staleTime: 3 * 60 * 1000, - enabled: !hasSearch || isSearchValid, + enabled: includeSurfsenseDocs && (!hasSearch || isSearchValid), placeholderData: keepPreviousData, }); @@ -324,7 +338,7 @@ export const DocumentMentionPicker = forwardRef< if (currentPage !== 0) return; const combinedDocs: Pick<Document, "id" | "title" | "document_type">[] = []; - if (surfsenseDocs?.items) { + if (includeSurfsenseDocs && surfsenseDocs?.items) { for (const doc of surfsenseDocs.items) { combinedDocs.push({ id: doc.id, @@ -340,7 +354,7 @@ export const DocumentMentionPicker = forwardRef< } setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); - }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm]); + }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm, includeSurfsenseDocs]); const loadNextPage = useCallback(async () => { if (isLoadingMore || !hasMore) return; @@ -449,7 +463,7 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); - const showSurfsenseDocsRoot = surfsenseDocsList.length > 0; + const showSurfsenseDocsRoot = includeSurfsenseDocs && surfsenseDocsList.length > 0; const selectMention = useCallback( (mention: MentionedDocumentInfo) => { diff --git a/surfsense_web/lib/automations/builder-schema.ts b/surfsense_web/lib/automations/builder-schema.ts new file mode 100644 index 000000000..a6ed08c09 --- /dev/null +++ b/surfsense_web/lib/automations/builder-schema.ts @@ -0,0 +1,456 @@ +/** + * The form builder's own data model plus the mappers that bridge it to the + * backend contract (``automation.types.ts``). + * + * The builder deliberately exposes a *subset* of the full automation + * definition: a name, one or more natural-language agent tasks, a single + * schedule, and a few execution knobs. Anything richer (goal, per-step + * ``when`` predicates, ``inputs`` schema, ``on_failure`` steps, multiple or + * non-schedule triggers, custom metadata) is not representable here, so on + * edit we detect it and bounce the user to raw-JSON mode rather than silently + * dropping their data. ``goal`` is the one exception: it is carried through + * invisibly so the common drafter-produced automation stays form-editable. + */ + +import { z } from "zod"; +import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; +import { + type Automation, + type AutomationCreateRequest, + type AutomationDefinition, + type AutomationUpdateRequest, + execution as executionContract, + type TriggerCreateRequest, +} from "@/contracts/types/automation.types"; +import { DEFAULT_SCHEDULE, fromCron, type ScheduleModel, toCron } from "./schedule-builder"; + +const EXECUTION_DEFAULTS = executionContract.parse({}); + +// --------------------------------------------------------------------------- +// Form model +// --------------------------------------------------------------------------- + +export const builderTaskSchema = z.object({ + /** Client-side identity for stable React keys across reorder; not persisted. */ + id: z.string(), + query: z.string().trim().min(1, "Describe what the agent should do"), + /** + * Files / folders / connectors @-mentioned in the query. Mirrors the chat + * composer's mention list and is forwarded to the run as step params so the + * agent scopes retrieval to them. The query text already carries ``@Title`` + * for each; this is the structured side-channel of IDs. + */ + mentions: z.array(z.custom<MentionedDocumentInfo>()), + maxRetries: z.number().int().min(0).max(10).nullable(), + timeoutSeconds: z.number().int().positive().max(86_400).nullable(), +}); +export type BuilderTask = z.infer<typeof builderTaskSchema>; + +export const builderScheduleSchema = z.discriminatedUnion("mode", [ + z.object({ + mode: z.literal("preset"), + model: z.custom<ScheduleModel>(), + }), + z.object({ + mode: z.literal("cron"), + cron: z.string().trim().min(1, "Enter a schedule expression"), + }), +]); +export type BuilderSchedule = z.infer<typeof builderScheduleSchema>; + +export const builderExecutionSchema = z.object({ + timeoutSeconds: z.number().int().positive().max(86_400), + maxRetries: z.number().int().min(0).max(10), + retryBackoff: z.enum(["exponential", "linear", "none"]), + concurrency: z.enum(["drop_if_running", "queue", "always"]), +}); +export type BuilderExecution = z.infer<typeof builderExecutionSchema>; + +export const builderFormSchema = z.object({ + name: z.string().trim().min(1, "Give your automation a name").max(200), + description: z.string().trim().max(2000).nullable(), + tasks: z.array(builderTaskSchema).min(1, "Add at least one task"), + unattended: z.boolean(), + schedule: builderScheduleSchema.nullable(), + timezone: z.string().min(1), + execution: builderExecutionSchema, + tags: z.array(z.string()), + /** Carried through from an edited definition so we don't drop it. */ + goal: z.string().nullable(), +}); +export type BuilderForm = z.infer<typeof builderFormSchema>; + +// --------------------------------------------------------------------------- +// Defaults / construction +// --------------------------------------------------------------------------- + +export function getDefaultTimezone(): string { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; + } catch { + return "UTC"; + } +} + +export function getTimezones(): string[] { + try { + const supported = ( + Intl as unknown as { supportedValuesOf?: (key: string) => string[] } + ).supportedValuesOf?.("timeZone"); + if (supported && supported.length > 0) return supported; + } catch { + // fall through + } + return ["UTC", getDefaultTimezone()]; +} + +function newId(): string { + try { + return crypto.randomUUID(); + } catch { + return `task_${Math.random().toString(36).slice(2)}`; + } +} + +export function emptyTask(): BuilderTask { + return { id: newId(), query: "", mentions: [], maxRetries: null, timeoutSeconds: null }; +} + +export function createEmptyForm(): BuilderForm { + return { + name: "", + description: null, + tasks: [emptyTask()], + unattended: true, + schedule: { mode: "preset", model: { ...DEFAULT_SCHEDULE } }, + timezone: getDefaultTimezone(), + execution: { + timeoutSeconds: EXECUTION_DEFAULTS.timeout_seconds, + maxRetries: EXECUTION_DEFAULTS.max_retries, + retryBackoff: EXECUTION_DEFAULTS.retry_backoff, + concurrency: EXECUTION_DEFAULTS.concurrency, + }, + tags: [], + goal: null, + }; +} + +/** The cron string a schedule resolves to, regardless of preset/raw mode. */ +export function scheduleToCron(schedule: BuilderSchedule): string { + return schedule.mode === "preset" ? toCron(schedule.model) : schedule.cron.trim(); +} + +// --------------------------------------------------------------------------- +// Form -> contract payloads +// --------------------------------------------------------------------------- + +/** + * Project a task's @-mentions into the ``agent_task`` param fields the backend + * understands (the same names the chat ``new_chat`` request uses, minus + * SurfSense docs). Returns an empty object when there are no mentions so the + * params stay clean. ``mentioned_documents`` carries full chip metadata so the + * run can resolve titles/paths and the form can round-trip the chips back. + */ +function mentionParams(mentions: MentionedDocumentInfo[]): Record<string, unknown> { + if (mentions.length === 0) return {}; + const documentIds: number[] = []; + const folderIds: number[] = []; + const connectorIds: number[] = []; + const connectors: MentionedDocumentInfo[] = []; + for (const mention of mentions) { + if (mention.kind === "folder") { + folderIds.push(mention.id); + } else if (mention.kind === "connector") { + connectorIds.push(mention.id); + connectors.push(mention); + } else { + documentIds.push(mention.id); + } + } + const out: Record<string, unknown> = { mentioned_documents: mentions }; + if (documentIds.length > 0) out.mentioned_document_ids = documentIds; + if (folderIds.length > 0) out.mentioned_folder_ids = folderIds; + if (connectorIds.length > 0) { + out.mentioned_connector_ids = connectorIds; + out.mentioned_connectors = connectors; + } + return out; +} + +function buildPlan(form: BuilderForm) { + return form.tasks.map((task, index) => { + const step: Record<string, unknown> = { + step_id: `step_${index + 1}`, + action: "agent_task", + params: { + query: task.query.trim(), + auto_approve_all: form.unattended, + ...mentionParams(task.mentions), + }, + }; + if (task.maxRetries !== null) step.max_retries = task.maxRetries; + if (task.timeoutSeconds !== null) step.timeout_seconds = task.timeoutSeconds; + return step; + }); +} + +function buildDefinition(form: BuilderForm): AutomationDefinition { + return { + schema_version: "1.0", + name: form.name.trim(), + goal: form.goal, + // Triggers are attached at the top level of the create payload, not in + // the definition; the in-definition list stays empty. + triggers: [], + plan: buildPlan(form), + execution: { + timeout_seconds: form.execution.timeoutSeconds, + max_retries: form.execution.maxRetries, + retry_backoff: form.execution.retryBackoff, + concurrency: form.execution.concurrency, + on_failure: [], + }, + metadata: { tags: form.tags }, + } as unknown as AutomationDefinition; +} + +/** The desired schedule trigger for this form, or ``null`` if none. */ +export function buildScheduleTrigger(form: BuilderForm): TriggerCreateRequest | null { + if (!form.schedule) return null; + return { + type: "schedule", + params: { cron: scheduleToCron(form.schedule), timezone: form.timezone }, + static_inputs: {}, + enabled: true, + }; +} + +export function buildCreatePayload( + form: BuilderForm, + searchSpaceId: number +): AutomationCreateRequest { + const trigger = buildScheduleTrigger(form); + return { + search_space_id: searchSpaceId, + name: form.name.trim(), + description: form.description?.trim() ? form.description.trim() : null, + definition: buildDefinition(form), + triggers: trigger ? [trigger] : [], + }; +} + +export function buildUpdatePayload(form: BuilderForm): AutomationUpdateRequest { + return { + name: form.name.trim(), + description: form.description?.trim() ? form.description.trim() : null, + definition: buildDefinition(form), + }; +} + +// --------------------------------------------------------------------------- +// Contract -> form (edit hydration with safe fallback) +// --------------------------------------------------------------------------- + +export type HydrateResult = + | { formable: true; form: BuilderForm } + | { formable: false; reason: string }; + +/** A trigger as seen by the hydrator: both ``Trigger`` and ``TriggerCreateRequest`` fit. */ +export interface HydratableTrigger { + type: string; + params: Record<string, unknown>; +} + +const BACKOFF_VALUES = ["exponential", "linear", "none"] as const; +const CONCURRENCY_VALUES = ["drop_if_running", "queue", "always"] as const; + +function asRecord(value: unknown): Record<string, unknown> { + return value && typeof value === "object" ? (value as Record<string, unknown>) : {}; +} + +/** Best-effort projection of a stored ``mentioned_documents`` entry into a chip. */ +function coerceMention(raw: unknown): MentionedDocumentInfo | null { + const o = asRecord(raw); + if (typeof o.id !== "number" || typeof o.title !== "string") return null; + if (o.kind === "folder") { + return { id: o.id, title: o.title, kind: "folder" }; + } + if (o.kind === "connector") { + if (typeof o.connector_type !== "string" || typeof o.account_name !== "string") return null; + return { + id: o.id, + title: o.title, + kind: "connector", + connector_type: o.connector_type, + account_name: o.account_name, + }; + } + return { + id: o.id, + title: o.title, + kind: "doc", + document_type: typeof o.document_type === "string" ? o.document_type : "UNKNOWN", + }; +} + +/** + * Rebuild a task's mention chips from step params. Returns ``null`` when the + * step carries mention IDs that aren't backed by usable ``mentioned_documents`` + * metadata (e.g. hand-edited JSON), so the caller can fall back to JSON mode + * rather than silently dropping those IDs on the next save. + */ +function mentionsFromParams(params: Record<string, unknown>): MentionedDocumentInfo[] | null { + const rawList = Array.isArray(params.mentioned_documents) ? params.mentioned_documents : []; + const mentions: MentionedDocumentInfo[] = []; + for (const raw of rawList) { + const mention = coerceMention(raw); + if (mention) mentions.push(mention); + } + + const haveByKind = { + doc: new Set(mentions.filter((m) => m.kind === "doc").map((m) => m.id)), + folder: new Set(mentions.filter((m) => m.kind === "folder").map((m) => m.id)), + connector: new Set(mentions.filter((m) => m.kind === "connector").map((m) => m.id)), + }; + const idChecks: Array<[unknown, Set<number>]> = [ + [params.mentioned_document_ids, haveByKind.doc], + [params.mentioned_folder_ids, haveByKind.folder], + [params.mentioned_connector_ids, haveByKind.connector], + ]; + for (const [arr, have] of idChecks) { + if (!Array.isArray(arr)) continue; + for (const id of arr) { + if (typeof id === "number" && !have.has(id)) return null; + } + } + return mentions; +} + +/** + * Core projection of a definition + triggers into the builder form. Returns + * ``formable: false`` whenever something can't be represented, so the caller + * can drop into raw-JSON mode without losing data. Shared by the edit + * hydrator and the JSON-mode round-trip. + * + * The definition is read defensively (``unknown``) so a partially edited JSON + * tree can still round-trip into the form; completeness is enforced by the + * form's own validation at submit time, not here. + */ +export function hydrateForm( + name: string, + description: string | null, + def: unknown, + triggers: HydratableTrigger[] +): HydrateResult { + const d = asRecord(def); + + if (d.inputs) { + return { formable: false, reason: "uses an inputs schema" }; + } + + const exec = asRecord(d.execution); + const onFailure = Array.isArray(exec.on_failure) ? exec.on_failure : []; + if (onFailure.length > 0) { + return { formable: false, reason: "has on-failure steps" }; + } + + const metadata = asRecord(d.metadata); + const extraMetadataKeys = Object.keys(metadata).filter((key) => key !== "tags"); + if (extraMetadataKeys.length > 0) { + return { formable: false, reason: "has custom metadata" }; + } + + const plan = Array.isArray(d.plan) ? d.plan : []; + const tasks: BuilderTask[] = []; + let unattended = true; + for (const rawStep of plan) { + const step = asRecord(rawStep); + if (step.action !== "agent_task") { + return { formable: false, reason: `uses the "${String(step.action)}" action` }; + } + if (step.when) { + return { formable: false, reason: "uses conditional steps" }; + } + const params = asRecord(step.params); + const query = typeof params.query === "string" ? params.query : ""; + // auto_approve_all is a single global toggle in the form; if any step is + // explicitly false we surface the toggle as off. + if (params.auto_approve_all === false) unattended = false; + const mentions = mentionsFromParams(params); + if (mentions === null) { + return { formable: false, reason: "references mentions without metadata" }; + } + tasks.push({ + id: newId(), + query, + mentions, + maxRetries: typeof step.max_retries === "number" ? step.max_retries : null, + timeoutSeconds: typeof step.timeout_seconds === "number" ? step.timeout_seconds : null, + }); + } + if (tasks.length === 0) { + return { formable: false, reason: "has no steps" }; + } + + if (triggers.length > 1) { + return { formable: false, reason: "has multiple triggers" }; + } + const trigger = triggers[0]; + let schedule: BuilderSchedule | null = null; + let timezone = getDefaultTimezone(); + if (trigger) { + if (trigger.type !== "schedule") { + return { formable: false, reason: `has a "${trigger.type}" trigger` }; + } + const cron = typeof trigger.params?.cron === "string" ? trigger.params.cron : ""; + timezone = typeof trigger.params?.timezone === "string" ? trigger.params.timezone : timezone; + const model = fromCron(cron); + schedule = model ? { mode: "preset", model } : { mode: "cron", cron }; + } + + const retryBackoff = BACKOFF_VALUES.includes(exec.retry_backoff as never) + ? (exec.retry_backoff as BuilderExecution["retryBackoff"]) + : EXECUTION_DEFAULTS.retry_backoff; + const concurrency = CONCURRENCY_VALUES.includes(exec.concurrency as never) + ? (exec.concurrency as BuilderExecution["concurrency"]) + : EXECUTION_DEFAULTS.concurrency; + const tags = Array.isArray(metadata.tags) + ? metadata.tags.filter((tag): tag is string => typeof tag === "string") + : []; + + return { + formable: true, + form: { + name, + description: description ?? null, + tasks, + unattended, + schedule, + timezone, + execution: { + timeoutSeconds: + typeof exec.timeout_seconds === "number" + ? exec.timeout_seconds + : EXECUTION_DEFAULTS.timeout_seconds, + maxRetries: + typeof exec.max_retries === "number" ? exec.max_retries : EXECUTION_DEFAULTS.max_retries, + retryBackoff, + concurrency, + }, + tags, + goal: typeof d.goal === "string" ? d.goal : null, + }, + }; +} + +/** + * Project an existing automation into the builder form for editing. + */ +export function formFromAutomation(automation: Automation): HydrateResult { + return hydrateForm( + automation.name, + automation.description ?? null, + automation.definition, + automation.triggers ?? [] + ); +} diff --git a/surfsense_web/lib/automations/default-template.ts b/surfsense_web/lib/automations/default-template.ts deleted file mode 100644 index 8963992cb..000000000 --- a/surfsense_web/lib/automations/default-template.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Minimal valid ``AutomationCreate`` skeleton used to seed the raw-JSON - * create form. ``search_space_id`` is omitted on purpose — the form - * injects it from the route so users never have to know their id. - * - * The shape matches the Pydantic ``AutomationCreate`` model less the - * search_space_id field; Zod validates the merged payload before submit. - */ -export const DEFAULT_AUTOMATION_TEMPLATE = { - name: "My automation", - description: null, - definition: { - name: "My automation", - goal: null, - plan: [ - { - step_id: "step_1", - action: "agent_task", - params: { - query: "Summarize new docs added to folder 12 since the last run.", - }, - }, - ], - execution: { - timeout_seconds: 600, - max_retries: 2, - retry_backoff: "exponential", - concurrency: "drop_if_running", - on_failure: [], - }, - metadata: { tags: [] }, - }, - triggers: [ - { - type: "schedule", - params: { - cron: "0 9 * * 1-5", - timezone: "UTC", - }, - static_inputs: {}, - enabled: true, - }, - ], -} as const; diff --git a/surfsense_web/lib/automations/schedule-builder.ts b/surfsense_web/lib/automations/schedule-builder.ts new file mode 100644 index 000000000..37f4cfa14 --- /dev/null +++ b/surfsense_web/lib/automations/schedule-builder.ts @@ -0,0 +1,132 @@ +/** + * Bidirectional bridge between a friendly schedule model and the 5-field cron + * expression the backend ``schedule`` trigger expects (see + * ``app/automations/triggers/schedule/params.py``). + * + * The form builder never asks users to type cron. They pick a frequency + time + * (+ days), which ``toCron`` compiles. On edit we ``fromCron`` an existing + * expression back into the model; anything we don't recognize returns ``null`` + * so the caller can fall back to a raw-cron escape hatch instead of silently + * losing the user's schedule. + * + * The recognized patterns are intentionally the same family that + * ``describe-cron.ts`` humanizes, keeping the picker and the label in sync. + */ + +export type ScheduleFrequency = "hourly" | "daily" | "weekdays" | "weekly" | "monthly"; + +export interface ScheduleModel { + frequency: ScheduleFrequency; + /** 0-23. Ignored for ``hourly``. */ + hour: number; + /** 0-59. */ + minute: number; + /** 0 (Sun) - 6 (Sat). Used by ``weekly``. */ + daysOfWeek: number[]; + /** 1-31. Used by ``monthly``. */ + dayOfMonth: number; +} + +/** Sunday-first, matching cron's 0-6 day-of-week numbering. */ +export const WEEKDAY_OPTIONS: ReadonlyArray<{ value: number; short: string; long: string }> = [ + { value: 1, short: "Mon", long: "Monday" }, + { value: 2, short: "Tue", long: "Tuesday" }, + { value: 3, short: "Wed", long: "Wednesday" }, + { value: 4, short: "Thu", long: "Thursday" }, + { value: 5, short: "Fri", long: "Friday" }, + { value: 6, short: "Sat", long: "Saturday" }, + { value: 0, short: "Sun", long: "Sunday" }, +]; + +export const FREQUENCY_OPTIONS: ReadonlyArray<{ value: ScheduleFrequency; label: string }> = [ + { value: "hourly", label: "Every hour" }, + { value: "daily", label: "Every day" }, + { value: "weekdays", label: "Every weekday (Mon\u2013Fri)" }, + { value: "weekly", label: "Specific days of the week" }, + { value: "monthly", label: "Once a month" }, +]; + +export const DEFAULT_SCHEDULE: ScheduleModel = { + frequency: "weekdays", + hour: 9, + minute: 0, + daysOfWeek: [1], + dayOfMonth: 1, +}; + +function isInt(value: string): boolean { + return /^\d+$/.test(value); +} + +function clamp(value: number, min: number, max: number): number { + if (Number.isNaN(value)) return min; + return Math.min(max, Math.max(min, value)); +} + +/** Compile a schedule model into a 5-field cron expression. */ +export function toCron(model: ScheduleModel): string { + const minute = clamp(model.minute, 0, 59); + const hour = clamp(model.hour, 0, 23); + + switch (model.frequency) { + case "hourly": + return `${minute} * * * *`; + case "daily": + return `${minute} ${hour} * * *`; + case "weekdays": + return `${minute} ${hour} * * 1-5`; + case "weekly": { + const days = [...new Set(model.daysOfWeek)].sort((a, b) => a - b); + // Guard against an empty selection producing an invalid cron. + const dow = days.length > 0 ? days.join(",") : "1"; + return `${minute} ${hour} * * ${dow}`; + } + case "monthly": + return `${minute} ${hour} ${clamp(model.dayOfMonth, 1, 31)} * *`; + } +} + +/** + * Parse a 5-field cron expression back into a schedule model. Returns ``null`` + * for anything outside the recognized pattern family so callers can fall back + * to the raw-cron field. + */ +export function fromCron(cron: string): ScheduleModel | null { + const parts = cron.trim().split(/\s+/); + if (parts.length !== 5) return null; + + const [minute, hour, dom, month, dow] = parts; + + // Hourly: "M * * * *" + if (month === "*" && dom === "*" && dow === "*" && hour === "*" && isInt(minute)) { + return { ...DEFAULT_SCHEDULE, frequency: "hourly", minute: Number(minute) }; + } + + // Everything below requires concrete minute + hour. + if (!isInt(minute) || !isInt(hour)) return null; + + const base = { hour: Number(hour), minute: Number(minute) }; + + // Daily: "M H * * *" + if (month === "*" && dom === "*" && dow === "*") { + return { ...DEFAULT_SCHEDULE, ...base, frequency: "daily" }; + } + + // Weekdays: "M H * * 1-5" + if (month === "*" && dom === "*" && dow === "1-5") { + return { ...DEFAULT_SCHEDULE, ...base, frequency: "weekdays" }; + } + + // Weekly: "M H * * 1,3,5" + if (month === "*" && dom === "*" && /^[0-6](,[0-6])*$/.test(dow)) { + const daysOfWeek = [...new Set(dow.split(",").map(Number))].sort((a, b) => a - b); + return { ...DEFAULT_SCHEDULE, ...base, frequency: "weekly", daysOfWeek }; + } + + // Monthly: "M H D * *" + if (month === "*" && dow === "*" && isInt(dom)) { + return { ...DEFAULT_SCHEDULE, ...base, frequency: "monthly", dayOfMonth: Number(dom) }; + } + + return null; +} From 6b76f8c138eea5a9f0c7d102310286bcc62f2c75 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 21:29:24 -0700 Subject: [PATCH 092/133] refactor(automations): update icons and button labels in empty state and header components - Replaced the FileJson icon with SquarePen in both AutomationsEmptyState and AutomationsHeader components. - Updated button label from "Create via JSON" to "Create manually" for clarity in the automation creation process. --- .../automations/components/automations-empty-state.tsx | 6 +++--- .../automations/components/automations-header.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx index 83fa52fa8..cc54c5e94 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -1,5 +1,5 @@ "use client"; -import { FileJson, MessageSquarePlus, Workflow } from "lucide-react"; +import { MessageSquarePlus, SquarePen, Workflow } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -35,8 +35,8 @@ export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsE </Button> <Button asChild variant="outline"> <Link href={`/dashboard/${searchSpaceId}/automations/new`}> - <FileJson className="mr-2 h-4 w-4" /> - Create via JSON + <SquarePen className="mr-2 h-4 w-4" /> + Create manually </Link> </Button> </div> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index 544c6b7ac..8d5fab033 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -1,5 +1,5 @@ "use client"; -import { FileJson, MessageSquarePlus } from "lucide-react"; +import { MessageSquarePlus, SquarePen } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -42,8 +42,8 @@ export function AutomationsHeader({ <div className="flex items-center gap-2"> <Button asChild size="sm" variant="outline"> <Link href={`/dashboard/${searchSpaceId}/automations/new`}> - <FileJson className="mr-2 h-4 w-4" /> - Create via JSON + <SquarePen className="mr-2 h-4 w-4" /> + Create manually </Link> </Button> <Button asChild size="sm"> From 9b9e6828c7bb5f28da0ff2006f8689583377704a Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 21:44:22 -0700 Subject: [PATCH 093/133] refactor(automations): enhance mention handling in task parameters - Updated the `mentionParams` function to separate document and connector mentions, improving clarity and organization of the output. - Modified the `mentionsFromParams` function to correctly handle and categorize mentions from parameters, ensuring connectors are processed separately. - Adjusted documentation comments for better understanding of the changes in mention handling. --- .../lib/automations/builder-schema.ts | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/surfsense_web/lib/automations/builder-schema.ts b/surfsense_web/lib/automations/builder-schema.ts index a6ed08c09..a9349c56f 100644 --- a/surfsense_web/lib/automations/builder-schema.ts +++ b/surfsense_web/lib/automations/builder-schema.ts @@ -148,26 +148,33 @@ export function scheduleToCron(schedule: BuilderSchedule): string { * Project a task's @-mentions into the ``agent_task`` param fields the backend * understands (the same names the chat ``new_chat`` request uses, minus * SurfSense docs). Returns an empty object when there are no mentions so the - * params stay clean. ``mentioned_documents`` carries full chip metadata so the - * run can resolve titles/paths and the form can round-trip the chips back. + * params stay clean. + * + * ``mentioned_documents`` carries doc/folder chip metadata (so the run can + * resolve titles to paths); connectors live only in ``mentioned_connectors`` / + * ``mentioned_connector_ids`` to avoid duplicating them across buckets. */ function mentionParams(mentions: MentionedDocumentInfo[]): Record<string, unknown> { if (mentions.length === 0) return {}; const documentIds: number[] = []; const folderIds: number[] = []; const connectorIds: number[] = []; + const documents: MentionedDocumentInfo[] = []; const connectors: MentionedDocumentInfo[] = []; for (const mention of mentions) { if (mention.kind === "folder") { folderIds.push(mention.id); + documents.push(mention); } else if (mention.kind === "connector") { connectorIds.push(mention.id); connectors.push(mention); } else { documentIds.push(mention.id); + documents.push(mention); } } - const out: Record<string, unknown> = { mentioned_documents: mentions }; + const out: Record<string, unknown> = {}; + if (documents.length > 0) out.mentioned_documents = documents; if (documentIds.length > 0) out.mentioned_document_ids = documentIds; if (folderIds.length > 0) out.mentioned_folder_ids = folderIds; if (connectorIds.length > 0) { @@ -294,17 +301,26 @@ function coerceMention(raw: unknown): MentionedDocumentInfo | null { } /** - * Rebuild a task's mention chips from step params. Returns ``null`` when the - * step carries mention IDs that aren't backed by usable ``mentioned_documents`` - * metadata (e.g. hand-edited JSON), so the caller can fall back to JSON mode - * rather than silently dropping those IDs on the next save. + * Rebuild a task's mention chips from step params. Doc/folder chips come from + * ``mentioned_documents``; connector chips from ``mentioned_connectors`` (kept + * in their own bucket). Returns ``null`` when the step carries mention IDs that + * aren't backed by usable chip metadata (e.g. hand-edited JSON), so the caller + * can fall back to JSON mode rather than silently dropping those IDs on save. */ function mentionsFromParams(params: Record<string, unknown>): MentionedDocumentInfo[] | null { - const rawList = Array.isArray(params.mentioned_documents) ? params.mentioned_documents : []; const mentions: MentionedDocumentInfo[] = []; - for (const raw of rawList) { + const docList = Array.isArray(params.mentioned_documents) ? params.mentioned_documents : []; + for (const raw of docList) { const mention = coerceMention(raw); - if (mention) mentions.push(mention); + // Connectors belong in their own bucket; ignore any that leak in here. + if (mention && mention.kind !== "connector") mentions.push(mention); + } + const connectorList = Array.isArray(params.mentioned_connectors) + ? params.mentioned_connectors + : []; + for (const raw of connectorList) { + const mention = coerceMention(raw); + if (mention && mention.kind === "connector") mentions.push(mention); } const haveByKind = { From 40ca9e6ed2d17b7a112fb9424c31fbedc9ae1e28 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 22:35:14 -0700 Subject: [PATCH 094/133] refactor: remove `search_surfsense_docs` tool and related references - Deleted the `search_surfsense_docs` tool and its associated files, streamlining the agent's toolset. - Updated various components and prompts to remove references to the now-removed tool, ensuring consistency across the codebase. - Adjusted documentation to direct users to the SurfSense documentation link for product-related queries instead. --- .../146_drop_surfsense_docs_tables.py | 129 +++++++++ .../system_prompt/prompts/citations/on.md | 4 +- .../prompts/dynamic_context/private.md | 4 +- .../prompts/dynamic_context/team.md | 4 +- .../system_prompt/prompts/kb_first.md | 8 +- .../prompts/providers/anthropic.md | 2 +- .../prompts/providers/deepseek.md | 2 +- .../system_prompt/prompts/providers/google.md | 2 +- .../prompts/providers/openai_classic.md | 5 +- .../system_prompt/prompts/routing.md | 9 +- .../tools/search_surfsense_docs/__init__.py | 1 - .../search_surfsense_docs/description.md | 10 - .../tools/search_surfsense_docs/example.md | 15 -- .../main_agent/tools/index.py | 1 - .../builtins/research/system_prompt.md | 1 - .../builtins/research/tools/__init__.py | 4 +- .../builtins/research/tools/index.py | 2 - .../research/tools/search_surfsense_docs.py | 145 ---------- .../app/agents/new_chat/feature_flags.py | 2 +- .../app/agents/new_chat/mention_resolver.py | 10 +- .../new_chat/prompts/base/citations_on.md | 3 +- .../prompts/base/kb_only_policy_private.md | 2 +- .../prompts/base/kb_only_policy_team.md | 2 +- .../prompts/base/tool_routing_private.md | 1 + .../prompts/base/tool_routing_team.md | 1 + .../app/agents/new_chat/prompts/composer.py | 1 - .../prompts/examples/search_surfsense_docs.md | 9 - .../prompts/tools/search_surfsense_docs.md | 7 - .../skills/builtin/email-drafting/SKILL.md | 1 - .../skills/builtin/kb-research/SKILL.md | 2 +- .../skills/builtin/meeting-prep/SKILL.md | 2 +- .../skills/builtin/report-writing/SKILL.md | 2 +- .../skills/builtin/slack-summary/SKILL.md | 1 - .../app/agents/new_chat/subagents/config.py | 5 +- .../app/agents/new_chat/tools/__init__.py | 3 - .../app/agents/new_chat/tools/registry.py | 10 - .../new_chat/tools/search_surfsense_docs.py | 174 ------------ surfsense_backend/app/app.py | 8 - surfsense_backend/app/db.py | 45 ---- surfsense_backend/app/routes/__init__.py | 2 - .../app/routes/new_chat_routes.py | 2 - .../app/routes/surfsense_docs_routes.py | 172 ------------ surfsense_backend/app/schemas/new_chat.py | 4 - .../app/schemas/surfsense_docs.py | 43 --- .../app/tasks/chat/stream_new_chat.py | 112 +------- .../tasks/chat/streaming/context/__init__.py | 6 +- .../chat/streaming/context/mentioned_docs.py | 58 ---- .../flows/new_chat/initial_thinking_step.py | 26 +- .../streaming/flows/new_chat/input_state.py | 51 +--- .../streaming/flows/new_chat/orchestrator.py | 6 +- .../app/tasks/surfsense_docs_indexer.py | 249 ------------------ surfsense_backend/app/utils/surfsense_docs.py | 13 - .../scripts/seed_surfsense_docs.py | 40 --- .../test_default_permissions_layering.py | 1 - .../new_chat/test_specialized_subagents.py | 14 +- .../test_parallel_refactor_parity.py | 35 +-- .../components/builder/mention-task-input.tsx | 9 +- .../new-chat/[[...chat_id]]/page.tsx | 36 +-- .../atoms/chat/mentioned-documents.atom.ts | 5 +- .../assistant-ui/inline-citation.tsx | 159 +---------- .../components/assistant-ui/thread.tsx | 2 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 1 - .../new-chat/document-mention-picker.tsx | 113 ++------ .../contracts/enums/connectorIcons.tsx | 3 - surfsense_web/contracts/enums/toolIcons.tsx | 3 - .../contracts/types/document.types.ts | 55 ---- .../lib/apis/documents-api.service.ts | 46 ---- surfsense_web/lib/chat/thread-persistence.ts | 1 - .../lib/documents/document-type-labels.ts | 1 - surfsense_web/lib/query-client/cache-keys.ts | 1 - surfsense_web/tsc_out.txt | Bin 0 -> 32582 bytes 71 files changed, 232 insertions(+), 1676 deletions(-) create mode 100644 surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py delete mode 100644 surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md delete mode 100644 surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md delete mode 100644 surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py delete mode 100644 surfsense_backend/app/routes/surfsense_docs_routes.py delete mode 100644 surfsense_backend/app/schemas/surfsense_docs.py delete mode 100644 surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py delete mode 100644 surfsense_backend/app/tasks/surfsense_docs_indexer.py delete mode 100644 surfsense_backend/app/utils/surfsense_docs.py delete mode 100644 surfsense_backend/scripts/seed_surfsense_docs.py create mode 100644 surfsense_web/tsc_out.txt diff --git a/surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py b/surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py new file mode 100644 index 000000000..725405834 --- /dev/null +++ b/surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py @@ -0,0 +1,129 @@ +"""Drop Surfsense docs tables (feature removed end to end) + +Revision ID: 146 +Revises: 145 +Create Date: 2026-05-28 + +Removes the SurfSense product-documentation feature: the +``surfsense_docs_documents`` and ``surfsense_docs_chunks`` tables (created +in revision 60) and the GIN trigram index on the title column (added in +revision 67). The docs were seeded at startup from local MDX files, so no +user data is lost. Downgrade recreates the tables and indexes. +""" + +from collections.abc import Sequence + +from alembic import op +from app.config import config + +# revision identifiers, used by Alembic. +revision: str = "146" +down_revision: str | None = "145" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + +# Embedding dimension is required to recreate the vector columns on downgrade. +EMBEDDING_DIM = config.embedding_model_instance.dimension + + +def upgrade() -> None: + """Drop surfsense docs tables and all their indexes.""" + # Trigram index from revision 67 + op.execute("DROP INDEX IF EXISTS idx_surfsense_docs_title_trgm") + + # Full-text search indexes + op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_search_index") + op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_search_index") + + # Vector indexes + op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_vector_index") + op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_vector_index") + + # B-tree indexes + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_chunks_document_id") + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_updated_at") + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_content_hash") + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_source") + + # Tables (chunks first due to FK) + op.execute("DROP TABLE IF EXISTS surfsense_docs_chunks") + op.execute("DROP TABLE IF EXISTS surfsense_docs_documents") + + +def downgrade() -> None: + """Recreate surfsense docs tables and indexes (reverses revisions 60 + 67).""" + op.execute( + f""" + CREATE TABLE IF NOT EXISTS surfsense_docs_documents ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + source VARCHAR NOT NULL UNIQUE, + title VARCHAR NOT NULL, + content TEXT NOT NULL, + content_hash VARCHAR NOT NULL, + embedding vector({EMBEDDING_DIM}), + updated_at TIMESTAMP WITH TIME ZONE + ); + """ + ) + op.execute( + f""" + CREATE TABLE IF NOT EXISTS surfsense_docs_chunks ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + content TEXT NOT NULL, + embedding vector({EMBEDDING_DIM}), + document_id INTEGER NOT NULL REFERENCES surfsense_docs_documents(id) ON DELETE CASCADE + ); + """ + ) + + # B-tree indexes + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_documents_source ON surfsense_docs_documents(source)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_documents_content_hash ON surfsense_docs_documents(content_hash)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_documents_updated_at ON surfsense_docs_documents(updated_at)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_chunks_document_id ON surfsense_docs_chunks(document_id)" + ) + + # Vector indexes + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_documents_vector_index + ON surfsense_docs_documents USING hnsw (embedding public.vector_cosine_ops); + """ + ) + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_vector_index + ON surfsense_docs_chunks USING hnsw (embedding public.vector_cosine_ops); + """ + ) + + # Full-text search indexes + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_documents_search_index + ON surfsense_docs_documents USING gin (to_tsvector('english', content)); + """ + ) + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_search_index + ON surfsense_docs_chunks USING gin (to_tsvector('english', content)); + """ + ) + + # Trigram index from revision 67 + op.execute( + """ + CREATE INDEX IF NOT EXISTS idx_surfsense_docs_title_trgm + ON surfsense_docs_documents USING gin (title gin_trgm_ops); + """ + ) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md index e61a0bffb..2abd95d5a 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md @@ -4,8 +4,8 @@ never invent ids you didn't see. Citation ids are resolved by exact-match lookup; a wrong id silently breaks the link, so when in doubt, omit. ### Channel A — chunk blocks injected this turn -When `search_surfsense_docs` or `web_search` returns `<document>` / -`<chunk id='…'>` blocks in this turn: +When `web_search` returns `<document>` / `<chunk id='…'>` blocks in this +turn: 1. For each factual statement taken from those chunks, add `[citation:chunk_id]` using the **exact** id from a visible diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md index 71c86be40..8f2bfca4e 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md @@ -20,8 +20,8 @@ it to resolve paths the user describes in natural language ("my Q2 roadmap", delegating to a specialist. `<document>` and `<chunk id='…'>` blocks are chunked indexed content returned -by KB search (from `search_surfsense_docs`, or backing `<priority_documents>`). -Each chunk carries a stable `id` attribute. +by KB search (backing `<priority_documents>`). Each chunk carries a stable +`id` attribute. If a block doesn't appear this turn, work from the conversation alone. </dynamic_context> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md index 592c2ed9c..a5892c23a 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md @@ -20,8 +20,8 @@ week's planning notes") into concrete document references before delegating to a specialist. `<document>` and `<chunk id='…'>` blocks are chunked indexed content returned -by KB search (from `search_surfsense_docs`, or backing `<priority_documents>`). -Each chunk carries a stable `id` attribute. +by KB search (backing `<priority_documents>`). Each chunk carries a stable +`id` attribute. If a block doesn't appear this turn, work from the conversation alone. </dynamic_context> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md index f06a52c1d..80fa4bf8f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md @@ -1,19 +1,21 @@ <knowledge_base_first> CRITICAL — ground factual answers in what you actually receive this turn: - injected workspace context (see `<dynamic_context>`), -- results from your own tool calls (`search_surfsense_docs`, `web_search`, - `scrape_webpage`), +- results from your own tool calls (`web_search`, `scrape_webpage`), - or substantive summaries returned by a `task` specialist you invoked. Do **not** answer factual or informational questions from general knowledge unless the user explicitly authorises it after you say you couldn't find enough in those sources. The flow when nothing is found: -1. Say you couldn't find enough in their workspace, docs, or tool output. +1. Say you couldn't find enough in their workspace or tool output. 2. Ask: *"Would you like me to answer from my general knowledge instead?"* 3. Only answer from general knowledge after a clear yes. This rule does NOT apply to: casual conversation · meta-questions about SurfSense ("what can you do?") · formatting or analysis of content already in chat · clear rewrite/edit instructions · lightweight web research. + +For "how do I use SurfSense" / product-documentation questions, point the +user to https://www.surfsense.com/docs. </knowledge_base_first> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md index 89154c443..d852f5955 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md @@ -5,7 +5,7 @@ Structured reasoning: - For non-trivial work, `<thinking>` / short `<plan>` before tool calls is fine. Professional objectivity: -- Accuracy over flattery; verify with **search_surfsense_docs**, **web_search**, **scrape_webpage**, or **task** when unsure — don’t invent connector access. +- Accuracy over flattery; verify with **web_search**, **scrape_webpage**, or **task** when unsure — don’t invent connector access. Task management: - For 3+ steps, use todo tooling; update statuses promptly. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md index 4254e9ed5..01d56999f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md @@ -13,6 +13,6 @@ Attribution: Tool calls: - Parallelise independent calls. -- Prefer **search_surfsense_docs** for SurfSense docs/product questions before **web_search** when that fits the ask. +- For SurfSense docs/product questions, point the user to https://www.surfsense.com/docs. - Don’t invent paths, chunk ids, or URLs — only values from tools or the user. </provider_hints> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md index dc5073538..32ed959c1 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md @@ -7,7 +7,7 @@ Output style: - GitHub-flavoured Markdown; monospace-friendly. Workflow (Understand → Plan → Act → Verify): -1. **Understand:** parse the ask; use **search_surfsense_docs** / injected workspace context before guessing. +1. **Understand:** parse the ask; use injected workspace context before guessing. 2. **Plan:** for multi-step work, a short plan first. 3. **Act:** only with tools you actually have on this agent (see `<tools>` and `<tool_routing>`). Connector work → **task**. 4. **Verify:** re-read or re-search only when it materially reduces risk. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md index 7ff3ec912..8596c42cd 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md @@ -15,6 +15,7 @@ Output style: Tool calls: - Parallelise independent calls in one turn. -- Prefer **search_surfsense_docs** for SurfSense-product questions, **web_search** / **scrape_webpage** - for fresh public facts; integrations and heavy workflows → **task**. +- For SurfSense-product questions, point the user to https://www.surfsense.com/docs; + use **web_search** / **scrape_webpage** for fresh public facts; integrations and + heavy workflows → **task**. </provider_hints> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md index 1038dde3d..28cf0ac63 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md @@ -3,10 +3,7 @@ You have two execution channels. Pick the one that owns the work — never simulate one with the other. ### 1. Direct tools (you call them yourself) -- `search_surfsense_docs` — SurfSense product docs (setup, configuration, - connector docs, feature behavior). -- `web_search` — search the public web (anything outside SurfSense docs and - the workspace KB). +- `web_search` — search the public web (anything outside the workspace KB). - `scrape_webpage` — fetch the body of a specific public URL. - `update_memory` — curate persistent memory (see `<memory_protocol>`). - `write_todos` — maintain a structured plan when the turn series spans @@ -14,6 +11,10 @@ simulate one with the other. `in_progress` **before** the `task` call that handles it, `completed` once the call returns. Skip for single-step requests. +**Questions about how to use SurfSense itself** (setup, configuration, +connectors, feature behavior) — point the user to the documentation: +https://www.surfsense.com/docs. There is no docs-search tool; give the link. + **You have NO filesystem tools.** Any read, write, edit, move, rename, or search inside the user's workspace goes through `task(knowledge_base, …)` — never via `write_file`, `ls`, or any direct file operation. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py deleted file mode 100644 index c2cda318e..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""``search_surfsense_docs`` — description + few-shot examples.""" diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md deleted file mode 100644 index 256d3f3a4..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md +++ /dev/null @@ -1,10 +0,0 @@ -- `search_surfsense_docs` — Search official SurfSense documentation (product - help). - - Use when the user asks how SurfSense itself works — setup, configuration, - connector documentation, feature behavior, anything covered in the - product docs. - - Not a substitute for `task` when the user wants actions inside a - connected service (Gmail, Slack, Jira, Notion, etc.). - - Args: `query`, `top_k` (default 10). - - Returns doc excerpts; chunk ids may appear for attribution — see - `<citations>` for the contract. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md deleted file mode 100644 index d53ad8c91..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md +++ /dev/null @@ -1,15 +0,0 @@ -<example> -user: "How do I install SurfSense?" -→ search_surfsense_docs(query="installation setup") -</example> - -<example> -user: "What connectors does SurfSense support?" -→ search_surfsense_docs(query="available connectors integrations") -</example> - -<example> -user: "How do I set up the Notion connector?" -→ search_surfsense_docs(query="Notion connector setup configuration") -(Changing data inside Notion itself → `task(notion, …)`, not this tool.) -</example> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py index 88509eda7..70fb42c0d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py @@ -6,7 +6,6 @@ Connector integrations, MCP, deliverables, etc. are delegated via ``task`` subag from __future__ import annotations MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED: tuple[str, ...] = ( - "search_surfsense_docs", "web_search", "scrape_webpage", "update_memory", diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md index 3eabd8ee0..1b9ccaefa 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md @@ -8,7 +8,6 @@ Gather and synthesize evidence using SurfSense research tools with clear citatio <available_tools> - `web_search` - `scrape_webpage` -- `search_surfsense_docs` </available_tools> <tool_policy> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py index 414cc96f4..7234942b6 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py @@ -1,11 +1,9 @@ -"""Research-stage tools: web search, scrape, and in-product doc search.""" +"""Research-stage tools: web search and scrape.""" from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .web_search import create_web_search_tool __all__ = [ "create_scrape_webpage_tool", - "create_search_surfsense_docs_tool", "create_web_search_tool", ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py index ea544a8da..d8abce46c 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py @@ -9,7 +9,6 @@ from langchain_core.tools import BaseTool from app.agents.new_chat.permissions import Ruleset from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .web_search import create_web_search_tool NAME = "research" @@ -27,5 +26,4 @@ def load_tools( available_connectors=d.get("available_connectors"), ), create_scrape_webpage_tool(firecrawl_api_key=d.get("firecrawl_api_key")), - create_search_surfsense_docs_tool(db_session=d["db_session"]), ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py deleted file mode 100644 index ccc5c49e2..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Semantic search over pre-indexed in-app documentation chunks for user how-to questions.""" - -import asyncio -import json - -from langchain_core.tools import tool -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument -from app.utils.document_converters import embed_text -from app.utils.surfsense_docs import surfsense_docs_public_url - - -def format_surfsense_docs_results(results: list[tuple]) -> str: - """Format (chunk, document) rows as XML with ``doc-`` chunk IDs for citations and UI routing.""" - if not results: - return "No relevant Surfsense documentation found for your query." - - # Group chunks by document - grouped: dict[int, dict] = {} - for chunk, doc in results: - public_url = surfsense_docs_public_url(doc.source) - if doc.id not in grouped: - grouped[doc.id] = { - "document_id": f"doc-{doc.id}", - "document_type": "SURFSENSE_DOCS", - "title": doc.title, - "url": public_url, - "metadata": {"source": doc.source, "public_url": public_url}, - "chunks": [], - } - grouped[doc.id]["chunks"].append( - { - "chunk_id": f"doc-{chunk.id}", - "content": chunk.content, - } - ) - - # Render XML matching format_documents_for_context structure - parts: list[str] = [] - for g in grouped.values(): - metadata_json = json.dumps(g["metadata"], ensure_ascii=False) - - parts.append("<document>") - parts.append("<document_metadata>") - parts.append(f" <document_id>{g['document_id']}</document_id>") - parts.append(f" <document_type>{g['document_type']}</document_type>") - parts.append(f" <title><![CDATA[{g['title']}]]>") - parts.append(f" ") - parts.append(f" ") - parts.append("") - parts.append("") - parts.append("") - - for ch in g["chunks"]: - parts.append( - f" " - ) - - parts.append("") - parts.append("") - parts.append("") - - return "\n".join(parts).strip() - - -async def search_surfsense_docs_async( - query: str, - db_session: AsyncSession, - top_k: int = 10, -) -> str: - """ - Search Surfsense documentation using vector similarity. - - Args: - query: The search query about Surfsense usage - db_session: Database session for executing queries - top_k: Number of results to return - - Returns: - Formatted string with relevant documentation content - """ - # Get embedding for the query - query_embedding = await asyncio.to_thread(embed_text, query) - - # Vector similarity search on chunks, joining with documents - stmt = ( - select(SurfsenseDocsChunk, SurfsenseDocsDocument) - .join( - SurfsenseDocsDocument, - SurfsenseDocsChunk.document_id == SurfsenseDocsDocument.id, - ) - .order_by(SurfsenseDocsChunk.embedding.op("<=>")(query_embedding)) - .limit(top_k) - ) - - result = await db_session.execute(stmt) - rows = result.all() - - return format_surfsense_docs_results(rows) - - -def create_search_surfsense_docs_tool(db_session: AsyncSession): - """ - Factory function to create the search_surfsense_docs tool. - - Args: - db_session: Database session for executing queries - - Returns: - A configured tool function for searching Surfsense documentation - """ - - @tool - async def search_surfsense_docs(query: str, top_k: int = 10) -> str: - """ - Search Surfsense documentation for help with using the application. - - Use this tool when the user asks questions about: - - How to use Surfsense features - - Installation and setup instructions - - Configuration options and settings - - Troubleshooting common issues - - Available connectors and integrations - - Browser extension usage - - API documentation - - This searches the official Surfsense documentation that was indexed - at deployment time. It does NOT search the user's personal knowledge base. - - Args: - query: The search query about Surfsense usage or features - top_k: Number of documentation chunks to retrieve (default: 10) - - Returns: - Relevant documentation content formatted with chunk IDs for citations - """ - return await search_surfsense_docs_async( - query=query, - db_session=db_session, - top_k=top_k, - ) - - return search_surfsense_docs diff --git a/surfsense_backend/app/agents/new_chat/feature_flags.py b/surfsense_backend/app/agents/new_chat/feature_flags.py index 3cea051ef..27188fac3 100644 --- a/surfsense_backend/app/agents/new_chat/feature_flags.py +++ b/surfsense_backend/app/agents/new_chat/feature_flags.py @@ -104,7 +104,7 @@ class AgentFeatureFlags: # ``tools/google_drive``, ``tools/dropbox``, ``tools/onedrive``, # ``tools/google_calendar``, ``tools/confluence``, ``tools/discord``, # ``tools/teams``, ``tools/luma``, ``connected_accounts``, - # ``update_memory``, ``search_surfsense_docs``) now acquire fresh + # ``update_memory``) now acquire fresh # short-lived ``AsyncSession`` instances per call via # :data:`async_session_maker`. The factory still accepts ``db_session`` # for registry compatibility but ``del``'s it immediately — see any diff --git a/surfsense_backend/app/agents/new_chat/mention_resolver.py b/surfsense_backend/app/agents/new_chat/mention_resolver.py index 6a025b947..f13dbc6ae 100644 --- a/surfsense_backend/app/agents/new_chat/mention_resolver.py +++ b/surfsense_backend/app/agents/new_chat/mention_resolver.py @@ -73,9 +73,8 @@ class ResolvedMentionSet: ``@Project Roadmap`` is never shadowed by a shorter prefix ``@Project``). - ``mentioned_document_ids`` collapses doc + surfsense_doc chips into - a single ordered, deduped list because the priority middleware - treats them uniformly downstream — see + ``mentioned_document_ids`` is an ordered, deduped list consumed by + the priority middleware downstream — see ``KnowledgePriorityMiddleware._compute_priority_paths``. """ @@ -103,7 +102,6 @@ async def resolve_mentions( search_space_id: int, mentioned_documents: list[MentionedDocumentInfo] | None, mentioned_document_ids: list[int] | None = None, - mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, ) -> ResolvedMentionSet: """Resolve every @-mention chip on a turn into virtual paths. @@ -111,8 +109,7 @@ async def resolve_mentions( The function takes both the ``mentioned_documents`` discriminated list (chip metadata used for substitution + persistence) and the parallel id arrays (``mentioned_document_ids``, - ``mentioned_surfsense_doc_ids``, ``mentioned_folder_ids``) for two - reasons: + ``mentioned_folder_ids``) for two reasons: * Legacy clients that haven't migrated to the unified chip list still send the id arrays — we treat the union as authoritative. @@ -142,7 +139,6 @@ async def resolve_mentions( dict.fromkeys( [ *(mentioned_document_ids or []), - *(mentioned_surfsense_doc_ids or []), *chip_doc_ids, ] ) diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md b/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md index 56291bf3e..3562ce66e 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md @@ -59,14 +59,13 @@ Do NOT cite document_id. Always use the chunk id. - NEVER create your own citation format - use the exact chunk_id values from the documents in the [citation:chunk_id] format - NEVER format citations as clickable links or as markdown links like "([citation:5](https://example.com))". Always use plain square brackets only - NEVER make up chunk IDs if you are unsure about the chunk_id. It is better to omit the citation than to guess -- Copy the EXACT chunk id from the XML - if it says ``, use [citation:doc-123] +- Copy the EXACT chunk id from the XML - if it says ``, use [citation:5] - If the chunk id is a URL like ``, use [citation:https://example.com/page] CORRECT citation formats: - [citation:5] (numeric chunk ID from knowledge base) -- [citation:doc-123] (for Surfsense documentation chunks) - [citation:https://example.com/article] (URL chunk ID from web search results) - [citation:chunk_id1], [citation:chunk_id2], [citation:chunk_id3] (multiple citations) diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md index 9cc767e7e..073b75fa5 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md @@ -7,7 +7,7 @@ CRITICAL RULE — KNOWLEDGE BASE FIRST, NEVER DEFAULT TO GENERAL KNOWLEDGE: 2. Ask the user: "Would you like me to answer from my general knowledge instead?" 3. ONLY provide a general-knowledge answer AFTER the user explicitly says yes. - This policy does NOT apply to: - * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?") + * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?"). For "how do I use SurfSense" / product-documentation questions, point the user to https://www.surfsense.com/docs. * Formatting, summarization, or analysis of content already present in the conversation * Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points") * Tool-usage actions like generating reports, podcasts, images, or scraping webpages diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md index 1d806dbae..1a43ed490 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md @@ -7,7 +7,7 @@ CRITICAL RULE — KNOWLEDGE BASE FIRST, NEVER DEFAULT TO GENERAL KNOWLEDGE: 2. Ask: "Would you like me to answer from my general knowledge instead?" 3. ONLY provide a general-knowledge answer AFTER a team member explicitly says yes. - This policy does NOT apply to: - * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?") + * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?"). For "how do I use SurfSense" / product-documentation questions, point the user to https://www.surfsense.com/docs. * Formatting, summarization, or analysis of content already present in the conversation * Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points") * Tool-usage actions like generating reports, podcasts, images, or scraping webpages diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md index b8bb069e2..9121de879 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md @@ -13,6 +13,7 @@ When to use which tool: - Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Real-time public web data → call web_search - Reading a specific webpage → call scrape_webpage +- SurfSense product / how-to questions (setup, configuration, connectors, feature behavior) → point the user to the documentation: https://www.surfsense.com/docs **`task` subagents (when to delegate):** - **`linear_specialist`** — Linear-only investigations and tool use. diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md index b081a2123..c5383be77 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md @@ -13,6 +13,7 @@ When to use which tool: - Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Real-time public web data → call web_search - Reading a specific webpage → call scrape_webpage +- SurfSense product / how-to questions (setup, configuration, connectors, feature behavior) → point the user to the documentation: https://www.surfsense.com/docs **`task` subagents (when to delegate):** - **`linear_specialist`** — Linear-only investigations and tool use. diff --git a/surfsense_backend/app/agents/new_chat/prompts/composer.py b/surfsense_backend/app/agents/new_chat/prompts/composer.py index 42f8303e6..412665813 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/composer.py +++ b/surfsense_backend/app/agents/new_chat/prompts/composer.py @@ -151,7 +151,6 @@ def _read_fragment(subpath: str) -> str: # Ordered for reading flow: fundamentals first, then artifact generators, # then memory at the end (mirrors the legacy ``_ALL_TOOL_NAMES_ORDERED``). ALL_TOOL_NAMES_ORDERED: tuple[str, ...] = ( - "search_surfsense_docs", "web_search", "generate_podcast", "generate_video_presentation", diff --git a/surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md b/surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md deleted file mode 100644 index b90f2b7a7..000000000 --- a/surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md +++ /dev/null @@ -1,9 +0,0 @@ - -- User: "How do I install SurfSense?" - - Call: `search_surfsense_docs(query="installation setup")` -- User: "What connectors does SurfSense support?" - - Call: `search_surfsense_docs(query="available connectors integrations")` -- User: "How do I set up the Notion connector?" - - Call: `search_surfsense_docs(query="Notion connector setup configuration")` -- User: "How do I use Docker to run SurfSense?" - - Call: `search_surfsense_docs(query="Docker installation setup")` diff --git a/surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md b/surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md deleted file mode 100644 index 133717fec..000000000 --- a/surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md +++ /dev/null @@ -1,7 +0,0 @@ - -- search_surfsense_docs: Search the official SurfSense documentation. - - Use this tool when the user asks anything about SurfSense itself (the application they are using). - - Args: - - query: The search query about SurfSense - - top_k: Number of documentation chunks to retrieve (default: 10) - - Returns: Documentation content with chunk IDs for citations (prefixed with 'doc-', e.g., [citation:doc-123]) diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md index 32e599e98..2dbc8ec43 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md @@ -1,7 +1,6 @@ --- name: email-drafting description: Draft an email matching the user's voice, with structured intent and CTA -allowed-tools: search_surfsense_docs --- # Email drafting diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md index c268278ab..0f0b5ffbb 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md @@ -1,7 +1,7 @@ --- name: kb-research description: Structured approach to finding and synthesizing information from the user's knowledge base -allowed-tools: search_surfsense_docs, scrape_webpage, read_file, ls_tree, grep, web_search +allowed-tools: scrape_webpage, read_file, ls_tree, grep, web_search --- # Knowledge-base research diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md index 9657eb078..5a375fbde 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md @@ -1,7 +1,7 @@ --- name: meeting-prep description: Pull together briefing materials before a scheduled meeting -allowed-tools: search_surfsense_docs, web_search, scrape_webpage, read_file +allowed-tools: web_search, scrape_webpage, read_file --- # Meeting preparation diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md index 17ac2f391..cfea9593f 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md @@ -1,7 +1,7 @@ --- name: report-writing description: How to scope, draft, and revise a Markdown report artifact via generate_report -allowed-tools: generate_report, search_surfsense_docs, read_file +allowed-tools: generate_report, read_file --- # Report writing diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md index 33b9e72a2..1a4c3da9f 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md @@ -1,7 +1,6 @@ --- name: slack-summary description: Distill a Slack channel or thread into actionable summary -allowed-tools: search_surfsense_docs --- # Slack summarization diff --git a/surfsense_backend/app/agents/new_chat/subagents/config.py b/surfsense_backend/app/agents/new_chat/subagents/config.py index b993d2b06..2cfd47441 100644 --- a/surfsense_backend/app/agents/new_chat/subagents/config.py +++ b/surfsense_backend/app/agents/new_chat/subagents/config.py @@ -46,7 +46,6 @@ logger = logging.getLogger(__name__) # ``glob``, ``grep``) plus the SurfSense-side read tools. EXPLORE_READ_TOOLS: frozenset[str] = frozenset( { - "search_surfsense_docs", "web_search", "scrape_webpage", "read_file", @@ -61,7 +60,6 @@ EXPLORE_READ_TOOLS: frozenset[str] = frozenset( # is needed, the parent should hand off to ``explore`` first. REPORT_WRITER_TOOLS: frozenset[str] = frozenset( { - "search_surfsense_docs", "read_file", "generate_report", } @@ -222,7 +220,6 @@ EXPLORE_SYSTEM_PROMPT = """You are the **explore** subagent for SurfSense. Conduct read-only research across the user's knowledge base, the web, and any documents the parent agent has surfaced. Return a synthesized answer with explicit citations — never speculate beyond the sources you have actually inspected. ## Tools available -- `search_surfsense_docs` — fast hybrid search over the user's knowledge base. - `web_search` — only when the user's KB clearly does not contain the answer. - `scrape_webpage` — to read a URL the user or the search results provided. - `read_file`, `ls`, `glob`, `grep` — to inspect specific documents or trees the parent has flagged. @@ -242,7 +239,7 @@ Produce a single high-quality report deliverable using `generate_report`. The pa ## Workflow 1. **Outline first.** Before calling `generate_report`, write a one-paragraph outline of the sections you plan to produce. Confirm the outline reflects the parent's instructions. -2. **Source resolution.** Decide whether to call `search_surfsense_docs` and `read_file` for any final-checks, or whether the parent's earlier tool calls already cover the source set. +2. **Source resolution.** Decide whether to call `read_file` for any final-checks, or whether the parent's earlier tool calls already cover the source set. 3. **One report.** Call `generate_report` exactly once with `source_strategy` chosen per the topic and chat history (see the `report-writing` skill). 4. **Confirm.** End with a one-sentence summary in your final message — never paste the report back into chat; the artifact card renders itself. """ diff --git a/surfsense_backend/app/agents/new_chat/tools/__init__.py b/surfsense_backend/app/agents/new_chat/tools/__init__.py index bc444b0c0..4b5ae3706 100644 --- a/surfsense_backend/app/agents/new_chat/tools/__init__.py +++ b/surfsense_backend/app/agents/new_chat/tools/__init__.py @@ -5,7 +5,6 @@ This module contains all the tools available to the SurfSense agent. To add a new tool, see the documentation in registry.py. Available tools: -- search_surfsense_docs: Search Surfsense documentation for usage help - generate_podcast: Generate audio podcasts from content - generate_video_presentation: Generate video presentations with slides and narration - generate_image: Generate images from text descriptions using AI models @@ -31,7 +30,6 @@ from .registry import ( get_tool_by_name, ) from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .update_memory import create_update_memory_tool, create_update_team_memory_tool from .video_presentation import create_generate_video_presentation_tool @@ -47,7 +45,6 @@ __all__ = [ "create_generate_podcast_tool", "create_generate_video_presentation_tool", "create_scrape_webpage_tool", - "create_search_surfsense_docs_tool", "create_update_memory_tool", "create_update_team_memory_tool", "format_documents_for_context", diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index 8c263ca20..6f011e372 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -101,7 +101,6 @@ from .podcast import create_generate_podcast_tool from .report import create_generate_report_tool from .resume import create_generate_resume_tool from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .teams import ( create_list_teams_channels_tool, create_read_teams_messages_tool, @@ -258,15 +257,6 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ ), requires=[], ), - # Surfsense documentation search tool - ToolDefinition( - name="search_surfsense_docs", - description="Search Surfsense documentation for help with using the application", - factory=lambda deps: create_search_surfsense_docs_tool( - db_session=deps["db_session"], - ), - requires=["db_session"], - ), # ========================================================================= # SERVICE ACCOUNT DISCOVERY # Generic tool for the LLM to discover connected accounts and resolve diff --git a/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py b/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py deleted file mode 100644 index d8a0efac7..000000000 --- a/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -Surfsense documentation search tool. - -This tool allows the agent to search the pre-indexed Surfsense documentation -to help users with questions about how to use the application. - -The documentation is indexed at deployment time from MDX files and stored -in dedicated tables (surfsense_docs_documents, surfsense_docs_chunks). -""" - -import asyncio -import json - -from langchain_core.tools import tool -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument, async_session_maker -from app.utils.document_converters import embed_text -from app.utils.surfsense_docs import surfsense_docs_public_url - - -def format_surfsense_docs_results(results: list[tuple]) -> str: - """ - Format search results into XML structure for the LLM context. - - Uses the same XML structure as format_documents_for_context from knowledge_base.py - but with 'doc-' prefix on chunk IDs. This allows: - - LLM to use consistent [citation:doc-XXX] format - - Frontend to detect 'doc-' prefix and route to surfsense docs endpoint - - Args: - results: List of (chunk, document) tuples from the database query - - Returns: - Formatted XML string with documentation content and citation-ready chunks - """ - if not results: - return "No relevant Surfsense documentation found for your query." - - # Group chunks by document - grouped: dict[int, dict] = {} - for chunk, doc in results: - public_url = surfsense_docs_public_url(doc.source) - if doc.id not in grouped: - grouped[doc.id] = { - "document_id": f"doc-{doc.id}", - "document_type": "SURFSENSE_DOCS", - "title": doc.title, - "url": public_url, - "metadata": {"source": doc.source, "public_url": public_url}, - "chunks": [], - } - grouped[doc.id]["chunks"].append( - { - "chunk_id": f"doc-{chunk.id}", - "content": chunk.content, - } - ) - - # Render XML matching format_documents_for_context structure - parts: list[str] = [] - for g in grouped.values(): - metadata_json = json.dumps(g["metadata"], ensure_ascii=False) - - parts.append("") - parts.append("") - parts.append(f" {g['document_id']}") - parts.append(f" {g['document_type']}") - parts.append(f" <![CDATA[{g['title']}]]>") - parts.append(f" ") - parts.append(f" ") - parts.append("") - parts.append("") - parts.append("") - - for ch in g["chunks"]: - parts.append( - f" " - ) - - parts.append("") - parts.append("") - parts.append("") - - return "\n".join(parts).strip() - - -async def search_surfsense_docs_async( - query: str, - db_session: AsyncSession, - top_k: int = 10, -) -> str: - """ - Search Surfsense documentation using vector similarity. - - Args: - query: The search query about Surfsense usage - db_session: Database session for executing queries - top_k: Number of results to return - - Returns: - Formatted string with relevant documentation content - """ - # Get embedding for the query - query_embedding = await asyncio.to_thread(embed_text, query) - - # Vector similarity search on chunks, joining with documents - stmt = ( - select(SurfsenseDocsChunk, SurfsenseDocsDocument) - .join( - SurfsenseDocsDocument, - SurfsenseDocsChunk.document_id == SurfsenseDocsDocument.id, - ) - .order_by(SurfsenseDocsChunk.embedding.op("<=>")(query_embedding)) - .limit(top_k) - ) - - result = await db_session.execute(stmt) - rows = result.all() - - return format_surfsense_docs_results(rows) - - -def create_search_surfsense_docs_tool(db_session: AsyncSession): - """ - Factory function to create the search_surfsense_docs tool. - - The tool acquires its own short-lived ``AsyncSession`` per call via - :data:`async_session_maker` so the closure is safe to share across - HTTP requests by the compiled-agent cache. Capturing a per-request - session here would surface stale/closed sessions on cache hits. - - Args: - db_session: Reserved for registry compatibility. Per-call sessions - are opened via :data:`async_session_maker` inside the tool body. - - Returns: - A configured tool function for searching Surfsense documentation - """ - del db_session # per-call session — see docstring - - @tool - async def search_surfsense_docs(query: str, top_k: int = 10) -> str: - """ - Search Surfsense documentation for help with using the application. - - Use this tool when the user asks questions about: - - How to use Surfsense features - - Installation and setup instructions - - Configuration options and settings - - Troubleshooting common issues - - Available connectors and integrations - - Browser extension usage - - API documentation - - This searches the official Surfsense documentation that was indexed - at deployment time. It does NOT search the user's personal knowledge base. - - Args: - query: The search query about Surfsense usage or features - top_k: Number of documentation chunks to retrieve (default: 10) - - Returns: - Relevant documentation content formatted with chunk IDs for citations - """ - async with async_session_maker() as db_session: - return await search_surfsense_docs_async( - query=query, - db_session=db_session, - top_k=top_k, - ) - - return search_surfsense_docs diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index 43b0af7d2..223eb5a1b 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -43,7 +43,6 @@ from app.rate_limiter import get_real_client_ip, limiter from app.routes import router as crud_router from app.routes.auth_routes import router as auth_router from app.schemas import UserCreate, UserRead, UserUpdate -from app.tasks.surfsense_docs_indexer import seed_surfsense_docs from app.users import SECRET, auth_backend, current_active_user, fastapi_users from app.utils.perf import log_system_snapshot @@ -576,13 +575,6 @@ async def lifespan(app: FastAPI): initialize_llm_router() initialize_image_gen_router() initialize_vision_llm_router() - try: - await asyncio.wait_for(seed_surfsense_docs(), timeout=120) - except TimeoutError: - logging.getLogger(__name__).warning( - "Surfsense docs seeding timed out after 120s — skipping. " - "Docs will be indexed on the next restart." - ) # Phase 1.7 — JIT warmup. Bounded so a stuck warmup never delays # worker readiness. ``shield`` so Uvicorn cancelling startup diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index fe2e53268..d6ee9ff88 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -1150,46 +1150,6 @@ class Chunk(BaseModel, TimestampMixin): document = relationship("Document", back_populates="chunks") -class SurfsenseDocsDocument(BaseModel, TimestampMixin): - """ - Surfsense documentation storage. - Indexed at migration time from MDX files. - """ - - __tablename__ = "surfsense_docs_documents" - - source = Column( - String, nullable=False, unique=True, index=True - ) # File path: "connectors/slack.mdx" - title = Column(String, nullable=False) - content = Column(Text, nullable=False) - content_hash = Column(String, nullable=False, index=True) # For detecting changes - embedding = Column(Vector(config.embedding_model_instance.dimension)) - updated_at = Column(TIMESTAMP(timezone=True), nullable=True, index=True) - - chunks = relationship( - "SurfsenseDocsChunk", - back_populates="document", - cascade="all, delete-orphan", - ) - - -class SurfsenseDocsChunk(BaseModel, TimestampMixin): - """Chunk storage for Surfsense documentation.""" - - __tablename__ = "surfsense_docs_chunks" - - content = Column(Text, nullable=False) - embedding = Column(Vector(config.embedding_model_instance.dimension)) - - document_id = Column( - Integer, - ForeignKey("surfsense_docs_documents.id", ondelete="CASCADE"), - nullable=False, - ) - document = relationship("SurfsenseDocsDocument", back_populates="chunks") - - class Podcast(BaseModel, TimestampMixin): """Podcast model for storing generated podcasts.""" @@ -2680,11 +2640,6 @@ async def setup_indexes(): "CREATE INDEX IF NOT EXISTS idx_documents_search_space_updated ON documents (search_space_id, updated_at DESC NULLS LAST) INCLUDE (id, title, document_type)" ) ) - await conn.execute( - text( - "CREATE INDEX IF NOT EXISTS idx_surfsense_docs_title_trgm ON surfsense_docs_documents USING gin (title gin_trgm_ops)" - ) - ) async def create_db_and_tables(): diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 48a095456..8373f13c3 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -55,7 +55,6 @@ from .search_source_connectors_routes import router as search_source_connectors_ from .search_spaces_routes import router as search_spaces_router from .slack_add_connector_route import router as slack_add_connector_router from .stripe_routes import router as stripe_router -from .surfsense_docs_routes import router as surfsense_docs_router from .team_memory_routes import router as team_memory_router from .teams_add_connector_route import router as teams_add_connector_router from .video_presentations_routes import router as video_presentations_router @@ -108,7 +107,6 @@ router.include_router(new_llm_config_router) # LLM configs with prompt configur router.include_router(model_list_router) # Dynamic model catalogue from OpenRouter router.include_router(logs_router) router.include_router(circleback_webhook_router) # Circleback meeting webhooks -router.include_router(surfsense_docs_router) # Surfsense documentation for citations router.include_router(notifications_router) # Notifications with Zero sync router.include_router( mcp_oauth_router diff --git a/surfsense_backend/app/routes/new_chat_routes.py b/surfsense_backend/app/routes/new_chat_routes.py index fb4d5a049..63b7732a9 100644 --- a/surfsense_backend/app/routes/new_chat_routes.py +++ b/surfsense_backend/app/routes/new_chat_routes.py @@ -1785,7 +1785,6 @@ async def handle_new_chat( user_id=str(user.id), llm_config_id=llm_config_id, mentioned_document_ids=request.mentioned_document_ids, - mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids, mentioned_folder_ids=request.mentioned_folder_ids, mentioned_connector_ids=request.mentioned_connector_ids, mentioned_connectors=mentioned_connectors_payload, @@ -2278,7 +2277,6 @@ async def regenerate_response( user_id=str(user.id), llm_config_id=llm_config_id, mentioned_document_ids=request.mentioned_document_ids, - mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids, mentioned_folder_ids=request.mentioned_folder_ids, mentioned_connector_ids=request.mentioned_connector_ids, mentioned_connectors=mentioned_connectors_payload, diff --git a/surfsense_backend/app/routes/surfsense_docs_routes.py b/surfsense_backend/app/routes/surfsense_docs_routes.py deleted file mode 100644 index 0d5428dec..000000000 --- a/surfsense_backend/app/routes/surfsense_docs_routes.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -Routes for Surfsense documentation. - -These endpoints support the citation system for Surfsense docs, -allowing the frontend to fetch document details when a user clicks -on a [citation:doc-XXX] link. -""" - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy import func, select -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload - -from app.db import ( - SurfsenseDocsChunk, - SurfsenseDocsDocument, - User, - get_async_session, -) -from app.schemas import PaginatedResponse -from app.schemas.surfsense_docs import ( - SurfsenseDocsChunkRead, - SurfsenseDocsDocumentRead, - SurfsenseDocsDocumentWithChunksRead, -) -from app.users import current_active_user -from app.utils.surfsense_docs import surfsense_docs_public_url - -router = APIRouter() - - -@router.get( - "/surfsense-docs/by-chunk/{chunk_id}", - response_model=SurfsenseDocsDocumentWithChunksRead, -) -async def get_surfsense_doc_by_chunk_id( - chunk_id: int, - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -): - """ - Retrieves a Surfsense documentation document based on a chunk ID. - - This endpoint is used by the frontend to resolve [citation:doc-XXX] links. - """ - try: - # Get the chunk - chunk_result = await session.execute( - select(SurfsenseDocsChunk).filter(SurfsenseDocsChunk.id == chunk_id) - ) - chunk = chunk_result.scalars().first() - - if not chunk: - raise HTTPException( - status_code=404, - detail=f"Surfsense docs chunk with id {chunk_id} not found", - ) - - # Get the associated document with all its chunks - document_result = await session.execute( - select(SurfsenseDocsDocument) - .options(selectinload(SurfsenseDocsDocument.chunks)) - .filter(SurfsenseDocsDocument.id == chunk.document_id) - ) - document = document_result.scalars().first() - - if not document: - raise HTTPException( - status_code=404, - detail="Surfsense docs document not found", - ) - - # Sort chunks by ID - sorted_chunks = sorted(document.chunks, key=lambda x: x.id) - - return SurfsenseDocsDocumentWithChunksRead( - id=document.id, - title=document.title, - source=document.source, - public_url=surfsense_docs_public_url(document.source), - content=document.content, - chunks=[ - SurfsenseDocsChunkRead(id=c.id, content=c.content) - for c in sorted_chunks - ], - ) - except HTTPException: - raise - except Exception as e: - raise HTTPException( - status_code=500, - detail=f"Failed to retrieve Surfsense documentation: {e!s}", - ) from e - - -@router.get( - "/surfsense-docs", - response_model=PaginatedResponse[SurfsenseDocsDocumentRead], -) -async def list_surfsense_docs( - page: int = 0, - page_size: int = 50, - title: str | None = None, - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -): - """ - List all Surfsense documentation documents. - - Args: - page: Zero-based page index. - page_size: Number of items per page (default: 50). - title: Optional title filter (case-insensitive substring match). - session: Database session (injected). - user: Current authenticated user (injected). - - Returns: - PaginatedResponse[SurfsenseDocsDocumentRead]: Paginated list of Surfsense docs. - """ - try: - # Base query - query = select(SurfsenseDocsDocument) - count_query = select(func.count()).select_from(SurfsenseDocsDocument) - - # Filter by title if provided - if title and title.strip(): - query = query.filter(SurfsenseDocsDocument.title.ilike(f"%{title}%")) - count_query = count_query.filter( - SurfsenseDocsDocument.title.ilike(f"%{title}%") - ) - - # Get total count - total_result = await session.execute(count_query) - total = total_result.scalar() or 0 - - # Calculate offset - offset = page * page_size - - # Get paginated results - result = await session.execute( - query.order_by(SurfsenseDocsDocument.title).offset(offset).limit(page_size) - ) - docs = result.scalars().all() - - # Convert to response format - items = [ - SurfsenseDocsDocumentRead( - id=doc.id, - title=doc.title, - source=doc.source, - public_url=surfsense_docs_public_url(doc.source), - content=doc.content, - created_at=doc.created_at, - updated_at=doc.updated_at, - ) - for doc in docs - ] - - has_more = (offset + len(items)) < total - - return PaginatedResponse( - items=items, - total=total, - page=page, - page_size=page_size, - has_more=has_more, - ) - except Exception as e: - raise HTTPException( - status_code=500, - detail=f"Failed to list Surfsense documentation: {e!s}", - ) from e diff --git a/surfsense_backend/app/schemas/new_chat.py b/surfsense_backend/app/schemas/new_chat.py index 8b49413c6..ab95f9b6b 100644 --- a/surfsense_backend/app/schemas/new_chat.py +++ b/surfsense_backend/app/schemas/new_chat.py @@ -239,9 +239,6 @@ class NewChatRequest(BaseModel): mentioned_document_ids: list[int] | None = ( None # Optional document IDs mentioned with @ in the chat ) - mentioned_surfsense_doc_ids: list[int] | None = ( - None # Optional SurfSense documentation IDs mentioned with @ in the chat - ) mentioned_folder_ids: list[int] | None = Field( default=None, description=( @@ -326,7 +323,6 @@ class RegenerateRequest(BaseModel): None # New user query (for edit). None = reload with same query ) mentioned_document_ids: list[int] | None = None - mentioned_surfsense_doc_ids: list[int] | None = None mentioned_folder_ids: list[int] | None = Field( default=None, description=( diff --git a/surfsense_backend/app/schemas/surfsense_docs.py b/surfsense_backend/app/schemas/surfsense_docs.py deleted file mode 100644 index 3adf25032..000000000 --- a/surfsense_backend/app/schemas/surfsense_docs.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Schemas for Surfsense documentation. -""" - -from datetime import datetime - -from pydantic import BaseModel, ConfigDict - - -class SurfsenseDocsChunkRead(BaseModel): - """Schema for a Surfsense docs chunk.""" - - id: int - content: str - - model_config = ConfigDict(from_attributes=True) - - -class SurfsenseDocsDocumentRead(BaseModel): - """Schema for a Surfsense docs document (without chunks).""" - - id: int - title: str - source: str - public_url: str - content: str - created_at: datetime | None = None - updated_at: datetime | None = None - - model_config = ConfigDict(from_attributes=True) - - -class SurfsenseDocsDocumentWithChunksRead(BaseModel): - """Schema for a Surfsense docs document with its chunks.""" - - id: int - title: str - source: str - public_url: str - content: str - chunks: list[SurfsenseDocsChunkRead] - - model_config = ConfigDict(from_attributes=True) diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index 78f80c955..e150cf494 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -25,7 +25,6 @@ from uuid import UUID import anyio from langchain_core.messages import HumanMessage from sqlalchemy.future import select -from sqlalchemy.orm import selectinload from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent @@ -55,7 +54,6 @@ from app.db import ( NewChatThread, Report, SearchSourceConnectorType, - SurfsenseDocsDocument, async_session_maker, shielded_async_session, ) @@ -77,7 +75,6 @@ from app.tasks.chat.streaming.helpers.interrupt_inspector import ( ) from app.utils.content_utils import bootstrap_history_from_db from app.utils.perf import get_perf_logger, log_system_snapshot, trim_native_heap -from app.utils.surfsense_docs import surfsense_docs_public_url from app.utils.user_message_multimodal import build_human_message_content _background_tasks: set[asyncio.Task] = set() @@ -198,58 +195,6 @@ def _extract_chunk_parts(chunk: Any) -> dict[str, Any]: return out -def format_mentioned_surfsense_docs_as_context( - documents: list[SurfsenseDocsDocument], -) -> str: - """Format mentioned SurfSense documentation as context for the agent.""" - if not documents: - return "" - - context_parts = [""] - context_parts.append( - "The user has explicitly mentioned the following SurfSense documentation pages. " - "These are official documentation about how to use SurfSense and should be used to answer questions about the application. " - "Use [citation:CHUNK_ID] format for citations (e.g., [citation:doc-123])." - ) - - for doc in documents: - public_url = surfsense_docs_public_url(doc.source) - metadata_json = json.dumps( - {"source": doc.source, "public_url": public_url}, ensure_ascii=False - ) - - context_parts.append("") - context_parts.append("") - context_parts.append(f" doc-{doc.id}") - context_parts.append(" SURFSENSE_DOCS") - context_parts.append(f" <![CDATA[{doc.title}]]>") - context_parts.append(f" ") - context_parts.append( - f" " - ) - context_parts.append("") - context_parts.append("") - context_parts.append("") - - if hasattr(doc, "chunks") and doc.chunks: - for chunk in doc.chunks: - context_parts.append( - f" " - ) - else: - context_parts.append( - f" " - ) - - context_parts.append("") - context_parts.append("") - context_parts.append("") - - context_parts.append("") - - return "\n".join(context_parts) - - def extract_todos_from_deepagents(command_output) -> dict: """ Extract todos from deepagents' TodoListMiddleware Command output. @@ -837,7 +782,6 @@ async def stream_new_chat( user_id: str | None = None, llm_config_id: int = -1, mentioned_document_ids: list[int] | None = None, - mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, mentioned_connector_ids: list[int] | None = None, mentioned_connectors: list[dict[str, Any]] | None = None, @@ -869,7 +813,6 @@ async def stream_new_chat( llm_config_id: The LLM configuration ID (default: -1 for first global config) needs_history_bootstrap: If True, load message history from DB (for cloned chats) mentioned_document_ids: Optional list of document IDs mentioned with @ in the chat - mentioned_surfsense_doc_ids: Optional list of SurfSense doc IDs mentioned with @ in the chat mentioned_folder_ids: Optional list of knowledge-base folder IDs mentioned with @ (cloud mode) checkpoint_id: Optional checkpoint ID to rewind/fork from (for edit/reload operations) @@ -1295,19 +1238,7 @@ async def stream_new_chat( # Mentioned KB documents are now handled by KnowledgeBaseSearchMiddleware # which merges them into the scoped filesystem with full document - # structure. Only SurfSense docs and report context are inlined here. - - # Fetch mentioned SurfSense docs if any - mentioned_surfsense_docs: list[SurfsenseDocsDocument] = [] - if mentioned_surfsense_doc_ids: - result = await session.execute( - select(SurfsenseDocsDocument) - .options(selectinload(SurfsenseDocsDocument.chunks)) - .filter( - SurfsenseDocsDocument.id.in_(mentioned_surfsense_doc_ids), - ) - ) - mentioned_surfsense_docs = list(result.scalars().all()) + # structure. Only report context is inlined here. # Fetch the most recent report(s) in this thread so the LLM can # easily find report_id for versioning decisions, instead of @@ -1341,10 +1272,7 @@ async def stream_new_chat( agent_user_query = user_query accepted_folder_ids: list[int] = [] if fs_mode == FilesystemMode.CLOUD.value and ( - mentioned_document_ids - or mentioned_surfsense_doc_ids - or mentioned_folder_ids - or mentioned_documents + mentioned_document_ids or mentioned_folder_ids or mentioned_documents ): from app.schemas.new_chat import ( MentionedDocumentInfo as _MentionedDocumentInfo, @@ -1370,23 +1298,17 @@ async def stream_new_chat( search_space_id=search_space_id, mentioned_documents=chip_objs, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, ) agent_user_query = substitute_in_text(user_query, resolved.token_to_path) accepted_folder_ids = resolved.mentioned_folder_ids - # Format the user query with context (SurfSense docs + reports only). + # Format the user query with context (reports only). # Uses ``agent_user_query`` so the LLM sees backtick-wrapped paths # instead of bare ``@title`` tokens. final_query = agent_user_query context_parts = [] - if mentioned_surfsense_docs: - context_parts.append( - format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) - ) - if mentioned_connectors: connector_lines = [] for connector in mentioned_connectors: @@ -1617,12 +1539,8 @@ async def stream_new_chat( stream_result.content_builder = AssistantContentBuilder() # Initial thinking step - analyzing the request - if mentioned_surfsense_docs: - initial_title = "Analyzing referenced content" - action_verb = "Analyzing" - else: - initial_title = "Understanding your request" - action_verb = "Processing" + initial_title = "Understanding your request" + action_verb = "Processing" processing_parts = [] if user_query.strip(): @@ -1633,18 +1551,6 @@ async def stream_new_chat( else: processing_parts.append("(message)") - if mentioned_surfsense_docs: - doc_names = [] - for doc in mentioned_surfsense_docs: - title = doc.title - if len(title) > 30: - title = title[:27] + "..." - doc_names.append(title) - if len(doc_names) == 1: - processing_parts.append(f"[{doc_names[0]}]") - else: - processing_parts.append(f"[{len(doc_names)} docs]") - initial_items = [f"{action_verb}: {' '.join(processing_parts)}"] initial_step_id = "thinking-1" @@ -1664,10 +1570,10 @@ async def stream_new_chat( items=initial_items, ) - # These ORM objects (with eagerly-loaded chunks) can be very large. - # They're only needed to build context strings already copied into - # final_query / langchain_messages — release them before streaming. - del mentioned_surfsense_docs, recent_reports + # These ORM objects can be large. They're only needed to build context + # strings already copied into final_query / langchain_messages — + # release them before streaming. + del recent_reports del langchain_messages, final_query # Check if this is the first assistant response so we can generate diff --git a/surfsense_backend/app/tasks/chat/streaming/context/__init__.py b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py index f858a6c06..4cf58d76f 100644 --- a/surfsense_backend/app/tasks/chat/streaming/context/__init__.py +++ b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py @@ -1,15 +1,11 @@ -"""Pre-agent context shaping: mentioned-doc rendering and todos extraction.""" +"""Pre-agent context shaping: todos extraction.""" from __future__ import annotations from app.tasks.chat.streaming.context.deepagents_todos import ( extract_todos_from_deepagents, ) -from app.tasks.chat.streaming.context.mentioned_docs import ( - format_mentioned_surfsense_docs_as_context, -) __all__ = [ "extract_todos_from_deepagents", - "format_mentioned_surfsense_docs_as_context", ] diff --git a/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py b/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py deleted file mode 100644 index e02e98d34..000000000 --- a/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Render user-mentioned SurfSense docs as XML context for the agent.""" - -from __future__ import annotations - -import json - -from app.db import SurfsenseDocsDocument -from app.utils.surfsense_docs import surfsense_docs_public_url - - -def format_mentioned_surfsense_docs_as_context( - documents: list[SurfsenseDocsDocument], -) -> str: - if not documents: - return "" - - context_parts = [""] - context_parts.append( - "The user has explicitly mentioned the following SurfSense documentation pages. " - "These are official documentation about how to use SurfSense and should be used to answer questions about the application. " - "Use [citation:CHUNK_ID] format for citations (e.g., [citation:doc-123])." - ) - - for doc in documents: - public_url = surfsense_docs_public_url(doc.source) - metadata_json = json.dumps( - {"source": doc.source, "public_url": public_url}, ensure_ascii=False - ) - - context_parts.append("") - context_parts.append("") - context_parts.append(f" doc-{doc.id}") - context_parts.append(" SURFSENSE_DOCS") - context_parts.append(f" <![CDATA[{doc.title}]]>") - context_parts.append(f" ") - context_parts.append( - f" " - ) - context_parts.append("") - context_parts.append("") - context_parts.append("") - - if hasattr(doc, "chunks") and doc.chunks: - for chunk in doc.chunks: - context_parts.append( - f" " - ) - else: - context_parts.append( - f" " - ) - - context_parts.append("") - context_parts.append("") - context_parts.append("") - - context_parts.append("") - return "\n".join(context_parts) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py index c860e517e..e727200eb 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py @@ -1,8 +1,8 @@ """Build and emit the first ``thinking-1`` step for a new-chat turn. The step title and "Processing X" items are derived from what the user sent -(text snippet, image count, mentioned doc titles) so the FE can render a -meaningful placeholder while the agent stream warms up. +(text snippet, image count) so the FE can render a meaningful placeholder +while the agent stream warms up. ``thinking-1`` is the canonical id for this step — every subsequent ``thinking-N`` produced by ``stream_agent_events`` folds into the same @@ -15,7 +15,6 @@ from collections.abc import Iterator from dataclasses import dataclass from typing import Any -from app.db import SurfsenseDocsDocument from app.services.new_streaming_service import VercelStreamingService @@ -37,14 +36,9 @@ def build_initial_thinking_step( *, user_query: str, user_image_data_urls: list[str] | None, - mentioned_surfsense_docs: list[SurfsenseDocsDocument], ) -> InitialThinkingStep: - if mentioned_surfsense_docs: - title = "Analyzing referenced content" - action_verb = "Analyzing" - else: - title = "Understanding your request" - action_verb = "Processing" + title = "Understanding your request" + action_verb = "Processing" processing_parts: list[str] = [] if user_query.strip(): @@ -55,18 +49,6 @@ def build_initial_thinking_step( else: processing_parts.append("(message)") - if mentioned_surfsense_docs: - doc_names: list[str] = [] - for doc in mentioned_surfsense_docs: - t = doc.title - if len(t) > 30: - t = t[:27] + "..." - doc_names.append(t) - if len(doc_names) == 1: - processing_parts.append(f"[{doc_names[0]}]") - else: - processing_parts.append(f"[{len(doc_names)} docs]") - items = [f"{action_verb}: {' '.join(processing_parts)}"] return InitialThinkingStep(step_id="thinking-1", title=title, items=items) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py index f508571b0..0c6704bd1 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py @@ -5,20 +5,17 @@ Pipeline: 1. **History bootstrap** — only for cloned chats with no LangGraph checkpoint yet; flips the per-thread ``needs_history_bootstrap`` flag back to False once the rows are loaded. - 2. **Mentioned SurfSense docs** — eager-load chunks so the formatter has the - full content without a second roundtrip. - 3. **Recent reports** — top 3 by id desc with non-null content, so the LLM + 2. **Recent reports** — top 3 by id desc with non-null content, so the LLM can resolve ``report_id`` for versioning without spelunking history. - 4. **@-mention resolve** (cloud mode) — substitute ``@title`` tokens in the + 3. **@-mention resolve** (cloud mode) — substitute ``@title`` tokens in the query with canonical ``\`/documents/...\``` paths the LLM expects. - 5. **Context block render** — XML-wrap surfsense docs + reports, prepend to - the rewritten query, optionally prefix with display name for SEARCH_SPACE + 4. **Context block render** — XML-wrap recent reports, prepend to the + rewritten query, optionally prefix with display name for SEARCH_SPACE visibility. - 6. **HumanMessage** — multimodal content if images are attached. + 5. **HumanMessage** — multimodal content if images are attached. Returns the assembled ``input_state`` dict plus side-channel data the -orchestrator needs downstream (``accepted_folder_ids`` for runtime context; -``mentioned_surfsense_docs`` for the initial thinking step). +orchestrator needs downstream (``accepted_folder_ids`` for runtime context). """ from __future__ import annotations @@ -30,7 +27,6 @@ from typing import Any from langchain_core.messages import HumanMessage from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select -from sqlalchemy.orm import selectinload from app.agents.new_chat.filesystem_selection import FilesystemMode from app.agents.new_chat.mention_resolver import resolve_mentions, substitute_in_text @@ -38,10 +34,6 @@ from app.db import ( ChatVisibility, NewChatThread, Report, - SurfsenseDocsDocument, -) -from app.tasks.chat.streaming.context.mentioned_docs import ( - format_mentioned_surfsense_docs_as_context, ) from app.utils.content_utils import bootstrap_history_from_db from app.utils.user_message_multimodal import build_human_message_content @@ -55,13 +47,10 @@ class NewChatInputState: ``input_state`` is fed straight to the agent. ``accepted_folder_ids`` feeds the runtime context (the resolver may have dropped some chips). - ``mentioned_surfsense_docs`` is consumed by the initial thinking-step - builder for the FE placeholder before the agent stream starts. """ input_state: dict[str, Any] accepted_folder_ids: list[int] - mentioned_surfsense_docs: list[SurfsenseDocsDocument] async def build_new_chat_input_state( @@ -72,7 +61,6 @@ async def build_new_chat_input_state( user_query: str, user_image_data_urls: list[str] | None, mentioned_document_ids: list[int] | None, - mentioned_surfsense_doc_ids: list[int] | None, mentioned_folder_ids: list[int] | None, mentioned_documents: list[dict[str, Any]] | None, needs_history_bootstrap: bool, @@ -96,15 +84,6 @@ async def build_new_chat_input_state( thread.needs_history_bootstrap = False await session.commit() - mentioned_surfsense_docs: list[SurfsenseDocsDocument] = [] - if mentioned_surfsense_doc_ids: - result = await session.execute( - select(SurfsenseDocsDocument) - .options(selectinload(SurfsenseDocsDocument.chunks)) - .filter(SurfsenseDocsDocument.id.in_(mentioned_surfsense_doc_ids)) - ) - mentioned_surfsense_docs = list(result.scalars().all()) - # Top 3 reports keyed by id desc (newest first) with content present, # surfaced inline so the LLM resolves ``report_id`` for versioning without # digging through conversation history. @@ -125,14 +104,12 @@ async def build_new_chat_input_state( user_query=user_query, filesystem_mode=filesystem_mode, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, mentioned_documents=mentioned_documents, ) final_query = _render_query_with_context( agent_user_query=agent_user_query, - mentioned_surfsense_docs=mentioned_surfsense_docs, recent_reports=recent_reports, ) @@ -154,7 +131,6 @@ async def build_new_chat_input_state( return NewChatInputState( input_state=input_state, accepted_folder_ids=accepted_folder_ids, - mentioned_surfsense_docs=mentioned_surfsense_docs, ) @@ -165,7 +141,6 @@ async def _resolve_mentions_for_query( user_query: str, filesystem_mode: str, mentioned_document_ids: list[int] | None, - mentioned_surfsense_doc_ids: list[int] | None, mentioned_folder_ids: list[int] | None, mentioned_documents: list[dict[str, Any]] | None, ) -> tuple[str, list[int]]: @@ -187,10 +162,7 @@ async def _resolve_mentions_for_query( accepted_folder_ids: list[int] = [] has_any_mention = bool( - mentioned_document_ids - or mentioned_surfsense_doc_ids - or mentioned_folder_ids - or mentioned_documents + mentioned_document_ids or mentioned_folder_ids or mentioned_documents ) if filesystem_mode != FilesystemMode.CLOUD.value or not has_any_mention: return agent_user_query, accepted_folder_ids @@ -214,7 +186,6 @@ async def _resolve_mentions_for_query( search_space_id=search_space_id, mentioned_documents=chip_objs, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, ) agent_user_query = substitute_in_text(user_query, resolved.token_to_path) @@ -225,17 +196,11 @@ async def _resolve_mentions_for_query( def _render_query_with_context( *, agent_user_query: str, - mentioned_surfsense_docs: list[SurfsenseDocsDocument], recent_reports: list[Report], ) -> str: - """Prepend surfsense-docs + recent-reports XML blocks to the user query.""" + """Prepend recent-reports XML block to the user query.""" context_parts: list[str] = [] - if mentioned_surfsense_docs: - context_parts.append( - format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) - ) - if recent_reports: report_lines: list[str] = [] for r in recent_reports: diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py index 6d0853502..1892320d3 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py @@ -123,7 +123,6 @@ async def stream_new_chat( user_id: str | None = None, llm_config_id: int = -1, mentioned_document_ids: list[int] | None = None, - mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, mentioned_documents: list[dict[str, Any]] | None = None, checkpoint_id: str | None = None, @@ -435,7 +434,6 @@ async def stream_new_chat( user_query=user_query, user_image_data_urls=user_image_data_urls, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, mentioned_documents=mentioned_documents, needs_history_bootstrap=needs_history_bootstrap, @@ -447,7 +445,6 @@ async def stream_new_chat( ) input_state = assembled.input_state accepted_folder_ids = assembled.accepted_folder_ids - mentioned_surfsense_docs = assembled.mentioned_surfsense_docs _perf_log.info( "[stream_new_chat] History bootstrap + doc/report queries in %.3fs", time.perf_counter() - _t0, @@ -560,7 +557,6 @@ async def stream_new_chat( initial_step = build_initial_thinking_step( user_query=user_query, user_image_data_urls=user_image_data_urls, - mentioned_surfsense_docs=mentioned_surfsense_docs, ) for sse in iter_initial_thinking_step_frame( initial_step, @@ -575,7 +571,7 @@ async def stream_new_chat( # Drop the heavy ORM objects + the container that holds them so they # aren't retained for the entire streaming duration. ``input_state`` # already carries the langchain_messages list independently. - del assembled, mentioned_surfsense_docs + del assembled title_task = spawn_title_task( chat_id=chat_id, diff --git a/surfsense_backend/app/tasks/surfsense_docs_indexer.py b/surfsense_backend/app/tasks/surfsense_docs_indexer.py deleted file mode 100644 index db88c8700..000000000 --- a/surfsense_backend/app/tasks/surfsense_docs_indexer.py +++ /dev/null @@ -1,249 +0,0 @@ -""" -Surfsense documentation indexer. -Indexes MDX documentation files at startup. -""" - -import hashlib -import logging -import re -from datetime import UTC, datetime -from pathlib import Path - -from sqlalchemy import delete as sa_delete, select -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload -from sqlalchemy.orm.attributes import set_committed_value - -from app.config import config -from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument, async_session_maker -from app.utils.document_converters import embed_text - -logger = logging.getLogger(__name__) - - -async def _safe_set_docs_chunks( - session: AsyncSession, document: SurfsenseDocsDocument, chunks: list -) -> None: - """safe_set_chunks variant for the SurfsenseDocsDocument/Chunk models.""" - if document.id is not None: - await session.execute( - sa_delete(SurfsenseDocsChunk).where( - SurfsenseDocsChunk.document_id == document.id - ) - ) - for chunk in chunks: - chunk.document_id = document.id - - set_committed_value(document, "chunks", chunks) - session.add_all(chunks) - - -# Path to docs relative to project root -DOCS_DIR = ( - Path(__file__).resolve().parent.parent.parent.parent - / "surfsense_web" - / "content" - / "docs" -) - - -def parse_mdx_frontmatter(content: str) -> tuple[str, str]: - """ - Parse MDX file to extract frontmatter title and content. - - Args: - content: Raw MDX file content - - Returns: - Tuple of (title, content_without_frontmatter) - """ - # Match frontmatter between --- markers - frontmatter_pattern = r"^---\s*\n(.*?)\n---\s*\n" - match = re.match(frontmatter_pattern, content, re.DOTALL) - - if match: - frontmatter = match.group(1) - content_without_frontmatter = content[match.end() :] - - # Extract title from frontmatter - title_match = re.search(r"^title:\s*(.+)$", frontmatter, re.MULTILINE) - title = title_match.group(1).strip() if title_match else "Untitled" - - # Remove quotes if present - title = title.strip("\"'") - - return title, content_without_frontmatter.strip() - - return "Untitled", content.strip() - - -def get_all_mdx_files() -> list[Path]: - """ - Get all MDX files from the docs directory. - - Returns: - List of Path objects for each MDX file - """ - if not DOCS_DIR.exists(): - logger.warning(f"Docs directory not found: {DOCS_DIR}") - return [] - - return list(DOCS_DIR.rglob("*.mdx")) - - -def generate_surfsense_docs_content_hash(content: str) -> str: - """Generate SHA-256 hash for Surfsense docs content.""" - return hashlib.sha256(content.encode("utf-8")).hexdigest() - - -def create_surfsense_docs_chunks(content: str) -> list[SurfsenseDocsChunk]: - """ - Create chunks from Surfsense documentation content. - - Args: - content: Document content to chunk - - Returns: - List of SurfsenseDocsChunk objects with embeddings - """ - return [ - SurfsenseDocsChunk( - content=chunk.text, - embedding=embed_text(chunk.text), - ) - for chunk in config.chunker_instance.chunk(content) - ] - - -async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, int]: - """ - Index all Surfsense documentation files. - - Args: - session: SQLAlchemy async session - - Returns: - Tuple of (created, updated, skipped, deleted) counts - """ - created = 0 - updated = 0 - skipped = 0 - deleted = 0 - - # Get all existing docs from database - existing_docs_result = await session.execute( - select(SurfsenseDocsDocument).options( - selectinload(SurfsenseDocsDocument.chunks) - ) - ) - existing_docs = {doc.source: doc for doc in existing_docs_result.scalars().all()} - - # Track which sources we've processed - processed_sources = set() - - # Get all MDX files - mdx_files = get_all_mdx_files() - logger.info(f"Found {len(mdx_files)} MDX files to index") - - for mdx_file in mdx_files: - try: - source = str(mdx_file.relative_to(DOCS_DIR)) - processed_sources.add(source) - - # Read file content - raw_content = mdx_file.read_text(encoding="utf-8") - title, content = parse_mdx_frontmatter(raw_content) - content_hash = generate_surfsense_docs_content_hash(raw_content) - - if source in existing_docs: - existing_doc = existing_docs[source] - - # Check if content changed - if existing_doc.content_hash == content_hash: - logger.debug(f"Skipping unchanged: {source}") - skipped += 1 - continue - - # Content changed - update document - logger.info(f"Updating changed document: {source}") - - # Create new chunks - chunks = create_surfsense_docs_chunks(content) - - # Update document fields - existing_doc.title = title - existing_doc.content = content - existing_doc.content_hash = content_hash - existing_doc.embedding = embed_text(content) - await _safe_set_docs_chunks(session, existing_doc, chunks) - existing_doc.updated_at = datetime.now(UTC) - - updated += 1 - else: - # New document - create it - logger.info(f"Creating new document: {source}") - - chunks = create_surfsense_docs_chunks(content) - - document = SurfsenseDocsDocument( - source=source, - title=title, - content=content, - content_hash=content_hash, - embedding=embed_text(content), - chunks=chunks, - updated_at=datetime.now(UTC), - ) - - session.add(document) - created += 1 - - except Exception as e: - logger.error(f"Error processing {mdx_file}: {e}", exc_info=True) - continue - - # Delete documents for removed files - for source, doc in existing_docs.items(): - if source not in processed_sources: - logger.info(f"Deleting removed document: {source}") - await session.delete(doc) - deleted += 1 - - # Commit all changes - await session.commit() - - logger.info( - f"Indexing complete: {created} created, {updated} updated, " - f"{skipped} skipped, {deleted} deleted" - ) - - return created, updated, skipped, deleted - - -async def seed_surfsense_docs() -> tuple[int, int, int, int]: - """ - Seed Surfsense documentation into the database. - - This function indexes all MDX files from the docs directory. - It handles creating, updating, and deleting docs based on content changes. - - Returns: - Tuple of (created, updated, skipped, deleted) counts - Returns (0, 0, 0, 0) if an error occurs - """ - logger.info("Starting Surfsense docs indexing...") - - try: - async with async_session_maker() as session: - created, updated, skipped, deleted = await index_surfsense_docs(session) - - logger.info( - f"Surfsense docs indexing complete: " - f"created={created}, updated={updated}, skipped={skipped}, deleted={deleted}" - ) - - return created, updated, skipped, deleted - - except Exception as e: - logger.error(f"Failed to seed Surfsense docs: {e}", exc_info=True) - return 0, 0, 0, 0 diff --git a/surfsense_backend/app/utils/surfsense_docs.py b/surfsense_backend/app/utils/surfsense_docs.py deleted file mode 100644 index 9a6ab11a9..000000000 --- a/surfsense_backend/app/utils/surfsense_docs.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Utilities for SurfSense's built-in documentation index.""" - -from pathlib import PurePosixPath - -DOCS_PUBLIC_ROOT = PurePosixPath("/docs") - - -def surfsense_docs_public_url(source: str) -> str: - """Return the public docs route for an indexed documentation source path.""" - docs_path = PurePosixPath(source).with_suffix("") - if docs_path.name == "index": - docs_path = docs_path.parent - return (DOCS_PUBLIC_ROOT / docs_path).as_posix() diff --git a/surfsense_backend/scripts/seed_surfsense_docs.py b/surfsense_backend/scripts/seed_surfsense_docs.py deleted file mode 100644 index 68899c2aa..000000000 --- a/surfsense_backend/scripts/seed_surfsense_docs.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -""" -Seed Surfsense documentation into the database. - -CLI wrapper for the seed_surfsense_docs function. -Can be run manually for debugging or re-indexing. - -Usage: - python scripts/seed_surfsense_docs.py -""" - -import asyncio -import sys -from pathlib import Path - -# Add the parent directory to the path so we can import app modules -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from app.tasks.surfsense_docs_indexer import seed_surfsense_docs - - -def main(): - """CLI entry point for seeding Surfsense docs.""" - print("=" * 50) - print(" Surfsense Documentation Seeding") - print("=" * 50) - - created, updated, skipped, deleted = asyncio.run(seed_surfsense_docs()) - - print() - print("Results:") - print(f" Created: {created}") - print(f" Updated: {updated}") - print(f" Skipped: {skipped}") - print(f" Deleted: {deleted}") - print("=" * 50) - - -if __name__ == "__main__": - main() diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py b/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py index ac6b5d95c..2f222e148 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py @@ -60,7 +60,6 @@ class TestReadOnlyToolsAllowed: "glob", "web_search", "scrape_webpage", - "search_surfsense_docs", "get_connected_accounts", "write_todos", "task", diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py b/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py index 3035cc8e0..3c7fe5336 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py @@ -22,12 +22,6 @@ from app.agents.new_chat.subagents.config import ( # --------------------------------------------------------------------------- -@tool -def search_surfsense_docs(query: str) -> str: - """Search the user's KB.""" - return "" - - @tool def web_search(query: str) -> str: """Search the public web.""" @@ -95,7 +89,6 @@ def generate_report(topic: str) -> str: ALL_TOOLS = [ - search_surfsense_docs, web_search, scrape_webpage, read_file, @@ -161,7 +154,7 @@ class TestReportWriterSubagent: names = {t.name for t in spec["tools"]} # type: ignore[index] assert names == REPORT_WRITER_TOOLS & {t.name for t in ALL_TOOLS} assert "generate_report" in names - assert "search_surfsense_docs" in names + assert "read_file" in names def test_deny_rules_block_writes_but_allow_generate_report(self) -> None: spec = build_report_writer_subagent(tools=ALL_TOOLS) @@ -272,9 +265,9 @@ class TestFilterToolsWarningSuppression: # Allowed set asks for two registry tools (one present, one # not) plus a bunch of middleware-provided names. _filter_tools( - [search_surfsense_docs], + [web_search], allowed_names={ - "search_surfsense_docs", + "web_search", "scrape_webpage", # legitimately missing → should warn "read_file", # mw-provided → suppressed "ls", @@ -322,7 +315,6 @@ class TestDenyPatternsCoverage: def test_deny_patterns_do_not_match_safe_read_tools(self) -> None: canonical_reads = [ - "search_surfsense_docs", "read_file", "ls_tree", "grep", diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py index ff4ca30df..e014bb911 100644 --- a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py @@ -25,7 +25,6 @@ from __future__ import annotations import asyncio import inspect -from dataclasses import dataclass from typing import Any from unittest.mock import AsyncMock, patch @@ -140,45 +139,28 @@ def test_orchestrators_are_async_generator_functions() -> None: # ------------------------------------------------------------ initial thinking -@dataclass -class _FakeSurfsenseDoc: - """Stand-in for ``SurfsenseDocsDocument`` with just the field we read.""" - - title: str - - @pytest.mark.parametrize( - "user_query, image_urls, docs, expected_title, expected_action", + "user_query, image_urls, expected_title, expected_action", [ - ("hello world", None, [], "Understanding your request", "Processing"), + ("hello world", None, "Understanding your request", "Processing"), ( "", ["data:image/png;base64,AAA"], - [], "Understanding your request", "Processing", ), - ("", None, [], "Understanding your request", "Processing"), - ( - "doc question", - None, - [_FakeSurfsenseDoc(title="My Doc")], - "Analyzing referenced content", - "Analyzing", - ), + ("", None, "Understanding your request", "Processing"), ], ) def test_initial_thinking_step_branches( user_query: str, image_urls: list[str] | None, - docs: list[Any], expected_title: str, expected_action: str, ) -> None: step = build_initial_thinking_step( user_query=user_query, user_image_data_urls=image_urls, - mentioned_surfsense_docs=docs, # type: ignore[arg-type] ) assert step.step_id == "thinking-1" assert step.title == expected_title @@ -191,7 +173,6 @@ def test_initial_thinking_step_truncates_long_query() -> None: step = build_initial_thinking_step( user_query=long_query, user_image_data_urls=None, - mentioned_surfsense_docs=[], ) # 80-char truncation + ellipsis, sandwiched after "Processing: ". assert "..." in step.items[0] @@ -200,16 +181,6 @@ def test_initial_thinking_step_truncates_long_query() -> None: assert payload.startswith("x" * 80) and payload.endswith("...") -def test_initial_thinking_step_collapses_many_doc_names() -> None: - docs = [_FakeSurfsenseDoc(title=f"Doc {i}") for i in range(5)] - step = build_initial_thinking_step( - user_query="q", - user_image_data_urls=None, - mentioned_surfsense_docs=docs, # type: ignore[arg-type] - ) - assert "[5 docs]" in step.items[0] - - # ------------------------------------------------------------ capability gate diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx index 312454056..c0651a90b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx @@ -89,10 +89,10 @@ function removeFirstToken(text: string, token: string): string { /** * Task input that reuses the chat ``@`` mention experience -- the same - * ``InlineMentionEditor`` + ``DocumentMentionPicker`` as the composer, minus - * SurfSense product docs. The editor is the source of truth while mounted; - * ``onChange`` reports both the plain text (chips rendered as ``@Title``) and - * the structured mention list so the builder can persist IDs for the run. + * ``InlineMentionEditor`` + ``DocumentMentionPicker`` as the composer. The + * editor is the source of truth while mounted; ``onChange`` reports both the + * plain text (chips rendered as ``@Title``) and the structured mention list + * so the builder can persist IDs for the run. */ export function MentionTaskInput({ searchSpaceId, @@ -233,7 +233,6 @@ export function MentionTaskInput({ documentsApiService.searchDocumentTitles({ queryParams: prefetchParams }), staleTime: 60 * 1000, }); - - queryClient.prefetchQuery({ - queryKey: ["surfsense-docs-mention", "", false], - queryFn: () => - documentsApiService.getSurfsenseDocs({ - queryParams: { page: 0, page_size: 20 }, - }), - staleTime: 3 * 60 * 1000, - }); }, [searchSpaceId, queryClient]); // Handle scroll to comment from URL query params (e.g., from inbox item click) @@ -949,7 +940,6 @@ export default function NewChatPage() { trackChatMessageSent(searchSpaceId, currentThreadId, { hasAttachments: userImages.length > 0, hasMentionedDocuments: - mentionedDocumentIds.surfsense_doc_ids.length > 0 || mentionedDocumentIds.document_ids.length > 0 || mentionedDocumentIds.folder_ids.length > 0 || mentionedDocumentIds.connector_ids.length > 0, @@ -1027,12 +1017,11 @@ export default function NewChatPage() { // Get mentioned document IDs for context (separate fields for backend) const hasDocumentIds = mentionedDocumentIds.document_ids.length > 0; - const hasSurfsenseDocIds = mentionedDocumentIds.surfsense_doc_ids.length > 0; const hasFolderIds = mentionedDocumentIds.folder_ids.length > 0; const hasConnectorIds = mentionedDocumentIds.connector_ids.length > 0; // Clear mentioned documents after capturing them - if (hasDocumentIds || hasSurfsenseDocIds || hasFolderIds || hasConnectorIds) { + if (hasDocumentIds || hasFolderIds || hasConnectorIds) { setMentionedDocuments([]); } @@ -1054,9 +1043,6 @@ export default function NewChatPage() { mentioned_document_ids: hasDocumentIds ? mentionedDocumentIds.document_ids : undefined, - mentioned_surfsense_doc_ids: hasSurfsenseDocIds - ? mentionedDocumentIds.surfsense_doc_ids - : undefined, mentioned_folder_ids: hasFolderIds ? mentionedDocumentIds.folder_ids : undefined, mentioned_connector_ids: hasConnectorIds ? mentionedDocumentIds.connector_ids @@ -1947,18 +1933,14 @@ export default function NewChatPage() { const selection = await getAgentFilesystemSelection(searchSpaceId, { localFilesystemEnabled, }); - // Partition the source mentions back into doc/surfsense_doc/folder - // id buckets so the regenerate route can pass them to - // ``stream_new_chat`` and the priority middleware sees the - // same ``[USER-MENTIONED]`` priority entries the original - // turn did. Without this partition the regenerate flow - // silently dropped the agent's mention awareness — same - // architectural bug we fixed on the new-chat path. - const regenerateSurfsenseDocIds = sourceMentionedDocs - .filter((d) => d.kind === "doc" && d.document_type === "SURFSENSE_DOCS") - .map((d) => d.id); + // Partition the source mentions back into doc/folder id buckets + // so the regenerate route can pass them to ``stream_new_chat`` + // and the priority middleware sees the same ``[USER-MENTIONED]`` + // priority entries the original turn did. Without this partition + // the regenerate flow silently dropped the agent's mention + // awareness — same architectural bug we fixed on the new-chat path. const regenerateDocIds = sourceMentionedDocs - .filter((d) => d.kind === "doc" && d.document_type !== "SURFSENSE_DOCS") + .filter((d) => d.kind === "doc") .map((d) => d.id); const regenerateFolderIds = sourceMentionedDocs .filter((d) => d.kind === "folder") @@ -1973,8 +1955,6 @@ export default function NewChatPage() { client_platform: selection.client_platform, local_filesystem_mounts: selection.local_filesystem_mounts, mentioned_document_ids: regenerateDocIds.length > 0 ? regenerateDocIds : undefined, - mentioned_surfsense_doc_ids: - regenerateSurfsenseDocIds.length > 0 ? regenerateSurfsenseDocIds : undefined, mentioned_folder_ids: regenerateFolderIds.length > 0 ? regenerateFolderIds : undefined, mentioned_connector_ids: regenerateConnectors.length > 0 ? regenerateConnectors.map((d) => d.id) : undefined, diff --git a/surfsense_web/atoms/chat/mentioned-documents.atom.ts b/surfsense_web/atoms/chat/mentioned-documents.atom.ts index 25d1e397a..cf1bd8bcf 100644 --- a/surfsense_web/atoms/chat/mentioned-documents.atom.ts +++ b/surfsense_web/atoms/chat/mentioned-documents.atom.ts @@ -102,10 +102,7 @@ export const mentionedDocumentIdsAtom = atom((get) => { const folders = deduped.filter((m) => m.kind === "folder"); const connectors = deduped.filter((m) => m.kind === "connector"); return { - surfsense_doc_ids: docs - .filter((doc) => doc.document_type === "SURFSENSE_DOCS") - .map((doc) => doc.id), - document_ids: docs.filter((doc) => doc.document_type !== "SURFSENSE_DOCS").map((doc) => doc.id), + document_ids: docs.map((doc) => doc.id), folder_ids: folders.map((f) => f.id), connector_ids: connectors.map((c) => c.id), connectors: connectors.map((c) => ({ diff --git a/surfsense_web/components/assistant-ui/inline-citation.tsx b/surfsense_web/components/assistant-ui/inline-citation.tsx index a788c0ce6..cbf3c82d6 100644 --- a/surfsense_web/components/assistant-ui/inline-citation.tsx +++ b/surfsense_web/components/assistant-ui/inline-citation.tsx @@ -1,16 +1,13 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; import { useSetAtom } from "jotai"; -import { ExternalLink, FileText } from "lucide-react"; -import dynamic from "next/dynamic"; +import { FileText } from "lucide-react"; import type { FC } from "react"; import { useState } from "react"; import { openCitationPanelAtom } from "@/atoms/citation/citation-panel.atom"; import { useCitationMetadata } from "@/components/assistant-ui/citation-metadata-context"; import { CitationPanelContent } from "@/components/citation-panel/citation-panel"; import { Citation } from "@/components/tool-ui/citation"; -import { CitationHoverPopover } from "@/components/tool-ui/citation/citation-hover-popover"; import { Button } from "@/components/ui/button"; import { Drawer, @@ -19,21 +16,8 @@ import { DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; -import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useMediaQuery } from "@/hooks/use-media-query"; -import { documentsApiService } from "@/lib/apis/documents-api.service"; -import { cacheKeys } from "@/lib/query-client/cache-keys"; - -// Lazily load MarkdownViewer here to break the static import cycle: -// `markdown-viewer.tsx` → `citation-renderer.tsx` → `inline-citation.tsx` -// would otherwise pull `markdown-viewer.tsx` back in at module-init time. -// Only `SurfsenseDocCitation` (popover body) ever renders this viewer, so -// the lazy boundary is invisible to most call paths. -const MarkdownViewer = dynamic( - () => import("@/components/markdown-viewer").then((m) => m.MarkdownViewer), - { ssr: false, loading: () => } -); interface InlineCitationProps { chunkId: number; @@ -41,9 +25,7 @@ interface InlineCitationProps { } /** - * Inline citation badge for knowledge-base chunks (numeric chunk IDs) and - * Surfsense documentation chunks (`isDocsChunk`). Negative chunk IDs render as - * a static "doc" pill (anonymous/synthetic uploads). + * Inline citation badge for knowledge-base chunks (numeric chunk IDs). * * Numeric KB chunks: clicking opens the citation panel in the right * sidebar (alongside the chat — does not replace it). The panel shows @@ -51,12 +33,13 @@ interface InlineCitationProps { * `chunk_window`), with the cited one highlighted and an option to * expand the window or jump into the full document via the editor panel. * - * Surfsense docs chunks: rendered as a hover-controlled shadcn Popover that - * lazily fetches and previews the cited chunk inline, since those docs aren't - * indexed into the user's search space and have no tab to open. + * Negative chunk IDs and legacy SurfSense-docs chunks (`isDocsChunk`) render + * as a static, non-interactive "doc" pill. The SurfSense product-docs feature + * was removed, so those markers are inert (no fetch, no preview) — they only + * survive in old persisted messages. */ export const InlineCitation: FC = ({ chunkId, isDocsChunk = false }) => { - if (chunkId < 0) { + if (chunkId < 0 || isDocsChunk) { return ( @@ -68,15 +51,11 @@ export const InlineCitation: FC = ({ chunkId, isDocsChunk = doc - Uploaded document + {isDocsChunk ? "Documentation reference" : "Uploaded document"} ); } - if (isDocsChunk) { - return ; - } - return ; }; @@ -127,128 +106,6 @@ const NumericChunkCitation: FC<{ chunkId: number }> = ({ chunkId }) => { ); }; -const SurfsenseDocCitation: FC<{ chunkId: number }> = ({ chunkId }) => { - const isTouchLike = useMediaQuery("(hover: none), (pointer: coarse)"); - const [mobilePreviewOpen, setMobilePreviewOpen] = useState(false); - const docQuery = useSurfsenseDocPreviewQuery(chunkId, mobilePreviewOpen); - - const handleMobileClick = () => { - setMobilePreviewOpen(true); - }; - - return ( - <> - ( - - )} - > - - - - - - - Surfsense documentation - - - - - - ); -}; - -function useSurfsenseDocPreviewQuery(chunkId: number, enabled = true) { - return useQuery({ - queryKey: cacheKeys.documents.byChunk(`doc-${chunkId}`), - queryFn: () => documentsApiService.getSurfsenseDocByChunk(chunkId), - staleTime: 5 * 60 * 1000, - enabled, - }); -} - -type SurfsenseDocPreviewQuery = ReturnType; - -const SurfsenseDocPreview: FC<{ chunkId: number }> = ({ chunkId }) => { - const query = useSurfsenseDocPreviewQuery(chunkId); - - return ; -}; - -const SurfsenseDocPreviewContent: FC<{ - chunkId: number; - query: SurfsenseDocPreviewQuery; - contentClassName?: string; -}> = ({ chunkId, query, contentClassName = "max-h-72" }) => { - const { data, isLoading, error } = query; - - const citedChunk = data?.chunks.find((c) => c.id === chunkId) ?? data?.chunks[0]; - - return ( - <> -
-
-

{data?.title ?? "Surfsense documentation"}

-

Chunk #{chunkId}

-
- {data?.public_url && ( - - - Open - - )} -
-
- {isLoading && ( -
- - Loading… -
- )} - {error && ( -

- {error instanceof Error ? error.message : "Failed to load chunk"} -

- )} - {!isLoading && !error && citedChunk?.content && ( - - )} - {!isLoading && !error && !citedChunk?.content && ( -

No content available.

- )} -
- - ); -}; - import { tryGetHostname } from "@/lib/url"; interface UrlCitationProps { diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 9abcfbb49..0336ffd35 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -1593,7 +1593,7 @@ interface ToolGroup { const TOOL_GROUPS: ToolGroup[] = [ { label: "Research", - tools: ["search_surfsense_docs", "scrape_webpage"], + tools: ["scrape_webpage"], }, { label: "Generate", diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 757ee2fc2..881fbe2b0 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -90,7 +90,6 @@ const DesktopLocalTabContent = dynamic( ); const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = [ - "SURFSENSE_DOCS", "USER_MEMORY", "TEAM_MEMORY", ]; diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 8e3fd4ca8..769327e1e 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -3,14 +3,7 @@ import { useQuery as useZeroQuery } from "@rocicorp/zero/react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; -import { - BookOpen, - ChevronLeft, - ChevronRight, - Files, - Folder as FolderIcon, - Unplug, -} from "lucide-react"; +import { ChevronLeft, ChevronRight, Files, Folder as FolderIcon, Unplug } from "lucide-react"; import { Fragment, forwardRef, @@ -57,13 +50,6 @@ interface DocumentMentionPickerProps { onDone: () => void; initialSelectedDocuments?: MentionedDocumentInfo[]; externalSearch?: string; - /** - * Whether to surface the "SurfSense Docs" (product documentation) branch - * and include those docs in search results. Defaults to ``true`` so the - * chat composer is unchanged; callers like the automation task input pass - * ``false`` to reference only the user's own knowledge base + connectors. - */ - includeSurfsenseDocs?: boolean; } const PAGE_SIZE = 20; @@ -74,7 +60,6 @@ const RECENTS_STORAGE_PREFIX = "surfsense:composer-mention-recents:v1:"; type BrowseView = | { kind: "root" } - | { kind: "surfsense-docs" } | { kind: "files-folders" } | { kind: "connectors" } | { kind: "connector-type"; connectorType: string; title: string }; @@ -241,7 +226,6 @@ export const DocumentMentionPicker = forwardRef< onDone, initialSelectedDocuments = [], externalSearch = "", - includeSurfsenseDocs = true, }, ref ) { @@ -298,15 +282,6 @@ export const DocumentMentionPicker = forwardRef< [searchSpaceId, debouncedSearch, isSearchValid] ); - const surfsenseDocsQueryParams = useMemo(() => { - const params: { page: number; page_size: number; title?: string } = { - page: 0, - page_size: PAGE_SIZE, - }; - if (isSearchValid) params.title = debouncedSearch.trim(); - return params; - }, [debouncedSearch, isSearchValid]); - const { data: titleSearchResults, isLoading: isTitleSearchLoading } = useQuery({ queryKey: ["document-titles", titleSearchParams], queryFn: ({ signal }) => @@ -316,15 +291,6 @@ export const DocumentMentionPicker = forwardRef< placeholderData: keepPreviousData, }); - const { data: surfsenseDocs, isLoading: isSurfsenseDocsLoading } = useQuery({ - queryKey: ["surfsense-docs-mention", debouncedSearch, isSearchValid], - queryFn: ({ signal }) => - documentsApiService.getSurfsenseDocs({ queryParams: surfsenseDocsQueryParams }, signal), - staleTime: 3 * 60 * 1000, - enabled: includeSurfsenseDocs && (!hasSearch || isSearchValid), - placeholderData: keepPreviousData, - }); - const filterBySearchTerm = useCallback( (docs: Pick[]) => { if (!isSearchValid) return docs; @@ -338,23 +304,13 @@ export const DocumentMentionPicker = forwardRef< if (currentPage !== 0) return; const combinedDocs: Pick[] = []; - if (includeSurfsenseDocs && surfsenseDocs?.items) { - for (const doc of surfsenseDocs.items) { - combinedDocs.push({ - id: doc.id, - title: doc.title, - document_type: "SURFSENSE_DOCS", - }); - } - } - if (titleSearchResults?.items) { combinedDocs.push(...titleSearchResults.items); setHasMore(titleSearchResults.has_more); } setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); - }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm, includeSurfsenseDocs]); + }, [titleSearchResults, currentPage, filterBySearchTerm]); const loadNextPage = useCallback(async () => { if (isLoadingMore || !hasMore) return; @@ -391,14 +347,6 @@ export const DocumentMentionPicker = forwardRef< return accumulatedDocuments.filter((doc) => doc.title.toLowerCase().includes(searchLower)); }, [accumulatedDocuments, deferredSearch, isSingleCharSearch]); - const surfsenseDocsList = useMemo( - () => actualDocuments.filter((doc) => doc.document_type === "SURFSENSE_DOCS"), - [actualDocuments] - ); - const userDocsList = useMemo( - () => actualDocuments.filter((doc) => doc.document_type !== "SURFSENSE_DOCS"), - [actualDocuments] - ); const folderMentions = useMemo(() => { const all = (zeroFolders ?? []).map((f) => makeFolderMention({ id: f.id, title: f.name })); if (!hasSearch) return all; @@ -463,7 +411,6 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); - const showSurfsenseDocsRoot = includeSurfsenseDocs && surfsenseDocsList.length > 0; const selectMention = useCallback( (mention: MentionedDocumentInfo) => { @@ -487,16 +434,6 @@ export const DocumentMentionPicker = forwardRef< const rootNodes = useMemo[]>(() => { const nodes: ComposerSuggestionNode[] = [...recentRootNodes]; - if (showSurfsenseDocsRoot) { - nodes.push({ - id: "surfsense-docs", - label: "SurfSense Docs", - subtitle: "Browse product documentation", - icon: , - type: "branch", - value: { kind: "view", view: { kind: "surfsense-docs" } }, - }); - } nodes.push( { id: "files-folders", @@ -519,7 +456,7 @@ export const DocumentMentionPicker = forwardRef< } ); return nodes; - }, [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot]); + }, [activeConnectors.length, recentRootNodes]); const searchNodes = useMemo[]>(() => { const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch) @@ -582,19 +519,6 @@ export const DocumentMentionPicker = forwardRef< const browseNodes = useMemo[]>(() => { if (view.kind === "root") return rootNodes; - if (view.kind === "surfsense-docs") { - return surfsenseDocsList.map((doc) => { - const mention = makeDocMention(doc); - return { - id: getMentionDocKey(mention), - label: doc.title, - icon: getConnectorIcon(doc.document_type, "size-4"), - type: "item" as const, - disabled: selectedKeys.has(getMentionDocKey(mention)), - value: { kind: "mention" as const, mention }, - }; - }); - } if (view.kind === "files-folders") { const folders = folderMentions.map((mention) => ({ id: getMentionDocKey(mention), @@ -605,7 +529,7 @@ export const DocumentMentionPicker = forwardRef< disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, })); - const docs = userDocsList.map((doc) => { + const docs = actualDocuments.map((doc) => { const mention = makeDocMention(doc); return { id: getMentionDocKey(mention), @@ -652,13 +576,12 @@ export const DocumentMentionPicker = forwardRef< }; }); }, [ + actualDocuments, activeConnectors, connectorTypeEntries, folderMentions, rootNodes, selectedKeys, - surfsenseDocsList, - userDocsList, view, ]); @@ -708,27 +631,23 @@ export const DocumentMentionPicker = forwardRef< const isRootBrowseView = !hasSearch && view.kind === "root"; const isVisibleViewLoading = hasSearch - ? isTitleSearchLoading || isSurfsenseDocsLoading || isConnectorsLoading - : view.kind === "surfsense-docs" - ? isSurfsenseDocsLoading - : view.kind === "files-folders" - ? isTitleSearchLoading - : view.kind === "connectors" || view.kind === "connector-type" - ? isConnectorsLoading - : false; + ? isTitleSearchLoading || isConnectorsLoading + : view.kind === "files-folders" + ? isTitleSearchLoading + : view.kind === "connectors" || view.kind === "connector-type" + ? isConnectorsLoading + : false; const actualLoading = isVisibleViewLoading && !isSingleCharSearch && visibleNodes.length === 0 && !isRootBrowseView; const title = hasSearch || view.kind === "root" ? null - : view.kind === "surfsense-docs" - ? "SurfSense Docs" - : view.kind === "files-folders" - ? "Files & Folders" - : view.kind === "connectors" - ? "Connectors" - : view.title; + : view.kind === "files-folders" + ? "Files & Folders" + : view.kind === "connectors" + ? "Connectors" + : view.title; return ( ; case "EXTENSION": return ; - case "SURFSENSE_DOCS": - return ; case "USER_MEMORY": case "TEAM_MEMORY": return ; diff --git a/surfsense_web/contracts/enums/toolIcons.tsx b/surfsense_web/contracts/enums/toolIcons.tsx index 668cb51cd..494c0eaee 100644 --- a/surfsense_web/contracts/enums/toolIcons.tsx +++ b/surfsense_web/contracts/enums/toolIcons.tsx @@ -1,5 +1,4 @@ import { - BookOpen, Brain, Calendar, FileEdit, @@ -47,7 +46,6 @@ const TOOL_ICONS: Record = { // Web / search scrape_webpage: ScanLine, web_search: Globe, - search_surfsense_docs: BookOpen, // Automations create_automation: Workflow, // Memory @@ -152,7 +150,6 @@ const TOOL_DISPLAY_NAMES: Record = { // Web / search scrape_webpage: "Read webpage", web_search: "Search the web", - search_surfsense_docs: "Search knowledge base", // Automations create_automation: "Create automation", // Memory diff --git a/surfsense_web/contracts/types/document.types.ts b/surfsense_web/contracts/types/document.types.ts index ccc15fa62..82c6cbdaf 100644 --- a/surfsense_web/contracts/types/document.types.ts +++ b/surfsense_web/contracts/types/document.types.ts @@ -27,7 +27,6 @@ export const documentTypeEnum = z.enum([ "CIRCLEBACK", "OBSIDIAN_CONNECTOR", "LOCAL_FOLDER_FILE", - "SURFSENSE_DOCS", "NOTE", "USER_MEMORY", "TEAM_MEMORY", @@ -77,27 +76,6 @@ export const documentWithChunks = document.extend({ chunk_start_index: z.number().optional().default(0), }); -/** - * Surfsense documentation schemas - * Follows the same pattern as document/documentWithChunks - */ -export const surfsenseDocsChunk = z.object({ - id: z.number(), - content: z.string(), -}); - -export const surfsenseDocsDocument = z.object({ - id: z.number(), - title: z.string(), - source: z.string(), - public_url: z.string(), - content: z.string(), -}); - -export const surfsenseDocsDocumentWithChunks = surfsenseDocsDocument.extend({ - chunks: z.array(surfsenseDocsChunk), -}); - /** * Get documents */ @@ -284,32 +262,6 @@ export const getDocumentChunksResponse = z.object({ has_more: z.boolean(), }); -/** - * Get Surfsense docs by chunk - */ -export const getSurfsenseDocsByChunkRequest = z.object({ - chunk_id: z.number(), -}); - -export const getSurfsenseDocsByChunkResponse = surfsenseDocsDocumentWithChunks; - -/** - * List Surfsense docs - */ -export const getSurfsenseDocsRequest = z.object({ - queryParams: paginationQueryParams.extend({ - title: z.string().optional(), - }), -}); - -export const getSurfsenseDocsResponse = z.object({ - items: z.array(surfsenseDocsDocument), - total: z.number(), - page: z.number(), - page_size: z.number(), - has_more: z.boolean(), -}); - /** * Update document */ @@ -358,13 +310,6 @@ export type DeleteDocumentResponse = z.infer; export type DocumentTypeEnum = z.infer; export type DocumentSortBy = z.infer; export type SortOrder = z.infer; -export type SurfsenseDocsChunk = z.infer; -export type SurfsenseDocsDocument = z.infer; -export type SurfsenseDocsDocumentWithChunks = z.infer; -export type GetSurfsenseDocsByChunkRequest = z.infer; -export type GetSurfsenseDocsByChunkResponse = z.infer; -export type GetSurfsenseDocsRequest = z.infer; -export type GetSurfsenseDocsResponse = z.infer; export type GetDocumentChunksRequest = z.infer; export type GetDocumentChunksResponse = z.infer; export type ChunkRead = z.infer; diff --git a/surfsense_web/lib/apis/documents-api.service.ts b/surfsense_web/lib/apis/documents-api.service.ts index 630c88d16..f9785c8a8 100644 --- a/surfsense_web/lib/apis/documents-api.service.ts +++ b/surfsense_web/lib/apis/documents-api.service.ts @@ -12,7 +12,6 @@ import { type GetDocumentsRequest, type GetDocumentsStatusRequest, type GetDocumentTypeCountsRequest, - type GetSurfsenseDocsRequest, getDocumentByChunkRequest, getDocumentByChunkResponse, getDocumentChunksRequest, @@ -25,9 +24,6 @@ import { getDocumentsStatusResponse, getDocumentTypeCountsRequest, getDocumentTypeCountsResponse, - getSurfsenseDocsByChunkResponse, - getSurfsenseDocsRequest, - getSurfsenseDocsResponse, type SearchDocumentsRequest, type SearchDocumentTitlesRequest, searchDocumentsRequest, @@ -363,48 +359,6 @@ class DocumentsApiService { ); }; - /** - * Get Surfsense documentation by chunk ID - * Used for resolving [citation:doc-XXX] citations - */ - getSurfsenseDocByChunk = async (chunkId: number) => { - return baseApiService.get( - `/api/v1/surfsense-docs/by-chunk/${chunkId}`, - getSurfsenseDocsByChunkResponse - ); - }; - - /** - * List all Surfsense documentation documents - * @param request - The request with query params - * @param signal - Optional AbortSignal for request cancellation - */ - getSurfsenseDocs = async (request: GetSurfsenseDocsRequest, signal?: AbortSignal) => { - const parsedRequest = getSurfsenseDocsRequest.safeParse(request); - - if (!parsedRequest.success) { - console.error("Invalid request:", parsedRequest.error); - - const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); - throw new ValidationError(`Invalid request: ${errorMessage}`); - } - - // Transform query params to be string values - const transformedQueryParams = parsedRequest.data.queryParams - ? Object.fromEntries( - Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [k, String(v)]) - ) - : undefined; - - const queryParams = transformedQueryParams - ? new URLSearchParams(transformedQueryParams).toString() - : ""; - - const url = `/api/v1/surfsense-docs?${queryParams}`; - - return baseApiService.get(url, getSurfsenseDocsResponse, { signal }); - }; - /** * Update a document */ diff --git a/surfsense_web/lib/chat/thread-persistence.ts b/surfsense_web/lib/chat/thread-persistence.ts index abe6bc02c..d30b87665 100644 --- a/surfsense_web/lib/chat/thread-persistence.ts +++ b/surfsense_web/lib/chat/thread-persistence.ts @@ -221,7 +221,6 @@ export interface RegenerateParams { content: string; }>; mentionedDocumentIds?: number[]; - mentionedSurfsenseDocIds?: number[]; } /** diff --git a/surfsense_web/lib/documents/document-type-labels.ts b/surfsense_web/lib/documents/document-type-labels.ts index 844961886..9e187f940 100644 --- a/surfsense_web/lib/documents/document-type-labels.ts +++ b/surfsense_web/lib/documents/document-type-labels.ts @@ -25,7 +25,6 @@ export function getDocumentTypeLabel(type: string): string { CIRCLEBACK: "Circleback", OBSIDIAN_CONNECTOR: "Obsidian", LOCAL_FOLDER_FILE: "Local Folder", - SURFSENSE_DOCS: "SurfSense Docs", NOTE: "Note", COMPOSIO_GOOGLE_DRIVE_CONNECTOR: "Composio Google Drive", COMPOSIO_GMAIL_CONNECTOR: "Composio Gmail", diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 8943d6842..35724cf94 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -30,7 +30,6 @@ export const cacheKeys = { withQueryParams: (queries: GetDocumentsRequest["queryParams"]) => ["documents-with-queries", ...stableEntries(queries)] as const, document: (documentId: string) => ["document", documentId] as const, - byChunk: (chunkId: string) => ["documents", "by-chunk", chunkId] as const, }, logs: { list: (searchSpaceId?: number | string) => ["logs", "list", searchSpaceId] as const, diff --git a/surfsense_web/tsc_out.txt b/surfsense_web/tsc_out.txt new file mode 100644 index 0000000000000000000000000000000000000000..c51e470851df2ea0ac136579d4d6112e183bad16 GIT binary patch literal 32582 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 LlrupwRW$TJ&LaAJ literal 0 HcmV?d00001 From 409fec94c329b15a7fbbab2ad55fce28e7293fc4 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Fri, 29 May 2026 03:13:46 -0700 Subject: [PATCH 095/133] feat(automations): implement model eligibility checks for automation creation - Added model eligibility checks to ensure automations can only use billable models (premium or BYOK). - Introduced new API endpoint to report model eligibility status for search spaces. - Updated frontend components to display eligibility alerts and disable creation options when models are not billable. - Enhanced automation creation forms to reflect model eligibility, preventing users from submitting invalid configurations. - Implemented server-side logic to capture and preserve model preferences across automation edits, ensuring consistent behavior during execution. --- .../main_agent/runtime/agent_cache.py | 7 +- .../main_agent/runtime/factory.py | 12 +- .../main_agent/tools/automation/create.py | 24 +- .../deliverables/tools/generate_image.py | 38 ++- .../builtins/deliverables/tools/index.py | 3 + .../actions/agent_task/dependencies.py | 45 +++- .../automations/actions/agent_task/invoke.py | 4 + .../app/automations/actions/types.py | 6 + .../app/automations/api/automation.py | 29 +++ .../app/automations/runtime/executor.py | 19 +- .../schemas/definition/__init__.py | 3 +- .../schemas/definition/envelope.py | 19 ++ .../app/automations/services/__init__.py | 12 + .../app/automations/services/automation.py | 64 ++++- .../app/automations/services/model_policy.py | 173 +++++++++++++ .../actions/agent_task/test_dependencies.py | 174 +++++++++++++ .../runtime/test_executor_action_ctx.py | 59 +++++ .../schemas/definition/test_envelope.py | 33 ++- .../unit/automations/services/__init__.py | 0 .../test_automation_service_policy.py | 236 ++++++++++++++++++ .../automations/services/test_model_policy.py | 196 +++++++++++++++ .../automations/automations-content.tsx | 25 +- .../automation-model-gate-alert.tsx | 61 +++++ .../components/automations-empty-state.tsx | 46 ++-- .../components/automations-header.tsx | 88 ++++++- .../builder/automation-builder-form.tsx | 52 +++- .../new/automation-new-content.tsx | 19 +- .../new-llm-config-mutation.atoms.ts | 6 + .../contracts/types/automation.types.ts | 30 +++ .../hooks/use-automation-model-eligibility.ts | 25 ++ .../lib/apis/automations-api.service.ts | 8 + surfsense_web/lib/query-client/cache-keys.ts | 2 + 32 files changed, 1451 insertions(+), 67 deletions(-) create mode 100644 surfsense_backend/app/automations/services/model_policy.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py create mode 100644 surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py create mode 100644 surfsense_backend/tests/unit/automations/services/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py create mode 100644 surfsense_backend/tests/unit/automations/services/test_model_policy.py create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-model-gate-alert.tsx create mode 100644 surfsense_web/hooks/use-automation-model-eligibility.ts diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py index 03cf7acb8..df1ee1b4c 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py @@ -57,6 +57,7 @@ async def build_agent_with_cache( mcp_tools_by_agent: dict[str, list[BaseTool]], disabled_tools: list[str] | None, config_id: str | None, + image_generation_config_id_override: int | None = None, ) -> Any: """Compile the multi-agent graph, serving from cache when key components are stable.""" @@ -91,7 +92,7 @@ async def build_agent_with_cache( # the key, otherwise a hit will leak state across threads. Bump the schema # version when the component list changes shape. cache_key = stable_hash( - "multi-agent-v1", + "multi-agent-v2", config_id, thread_id, user_id, @@ -109,6 +110,10 @@ async def build_agent_with_cache( system_prompt_hash(final_system_prompt), max_input_tokens, sorted(disabled_tools) if disabled_tools else None, + # Bound into the generate_image subagent tool at construction time, so it + # must key the compiled-agent cache to avoid leaking one automation's + # image model into another with the same config_id/search_space. + image_generation_config_id_override, ) return await get_cache().get_or_build(cache_key, builder=_build) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py index 8451b3b7d..44529d243 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py @@ -62,8 +62,14 @@ async def create_multi_agent_chat_deep_agent( mentioned_document_ids: list[int] | None = None, anon_session_id: str | None = None, filesystem_selection: FilesystemSelection | None = None, + image_generation_config_id: int | None = None, ): - """Deep agent with SurfSense tools/middleware; registry route subagents behind ``task`` when enabled.""" + """Deep agent with SurfSense tools/middleware; registry route subagents behind ``task`` when enabled. + + ``image_generation_config_id`` overrides the search space's image model for + this invocation (used by automations to run on their captured model). When + ``None``, the ``generate_image`` tool resolves the live search-space pref. + """ _t_agent_total = time.perf_counter() apply_litellm_prompt_caching(llm, agent_config=agent_config, thread_id=thread_id) @@ -129,6 +135,9 @@ async def create_multi_agent_chat_deep_agent( "available_document_types": available_document_types, "max_input_tokens": _max_input_tokens, "llm": llm, + # Per-invocation image model override (automations run on their captured + # model). Reaches the generate_image subagent tool via subagent_dependencies. + "image_generation_config_id_override": image_generation_config_id, } _t0 = time.perf_counter() @@ -285,6 +294,7 @@ async def create_multi_agent_chat_deep_agent( mcp_tools_by_agent=mcp_tools_by_agent, disabled_tools=disabled_tools, config_id=config_id, + image_generation_config_id_override=image_generation_config_id, ) _perf_log.info( "[create_agent] Middleware stack + graph compiled in %.3fs", diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py index 173d302e5..8e841c1e9 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -32,7 +32,8 @@ from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated impo ) from app.automations.schemas.api import AutomationCreate from app.automations.services.automation import AutomationService -from app.db import User, async_session_maker +from app.automations.services.model_policy import get_automation_model_eligibility +from app.db import SearchSpace, User, async_session_maker from app.utils.content_utils import extract_text_content from .prompt import build_draft_prompt @@ -98,6 +99,27 @@ def create_create_automation_tool( declined. Acknowledge once and stop — do NOT retry or pitch variants without a fresh user request. """ + # --- 0. Eligibility gate (fail fast, before drafting + HITL) --- + # Automations may only use premium or BYOK models. Check up front so we + # don't make the user draft + approve a card that can't be saved. + async with async_session_maker() as session: + search_space = await session.get(SearchSpace, search_space_id) + if search_space is None: + return { + "status": "error", + "message": "search space not found in this session", + } + eligibility = get_automation_model_eligibility(search_space) + if not eligibility["allowed"]: + reasons = " ".join(v["reason"] for v in eligibility["violations"]) + return { + "status": "error", + "message": ( + f"{reasons} Update the search space's model settings to a " + "premium or your own (BYOK) model, then try again." + ), + } + # --- 1. Draft via sub-LLM --- prompt = build_draft_prompt(search_space_id=search_space_id, intent=intent) try: diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py index f170a35db..094371760 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py @@ -63,8 +63,14 @@ def _get_global_image_gen_config(config_id: int) -> dict | None: def create_generate_image_tool( search_space_id: int, db_session: AsyncSession, + image_generation_config_id_override: int | None = None, ): - """Create ``generate_image`` with bound search space; DB work uses a per-call session.""" + """Create ``generate_image`` with bound search space; DB work uses a per-call session. + + ``image_generation_config_id_override``: when set (automations running on a + captured model), use this config id instead of reading the search space's + live ``image_generation_config_id``. + """ del db_session # use a fresh per-call session, see below @tool @@ -108,19 +114,27 @@ def create_generate_image_tool( # task's session is shared across every tool; without isolation, # autoflushes from a concurrent writer poison this tool too. async with shielded_async_session() as session: - result = await session.execute( - select(SearchSpace).filter(SearchSpace.id == search_space_id) - ) - search_space = result.scalars().first() - if not search_space: - return _failed( - {"error": "Search space not found"}, - error="Search space not found", + if image_generation_config_id_override is not None: + # Automation run: use the captured image model, insulated from + # later search-space changes. No search-space read needed. + config_id = ( + image_generation_config_id_override or IMAGE_GEN_AUTO_MODE_ID ) + else: + result = await session.execute( + select(SearchSpace).filter(SearchSpace.id == search_space_id) + ) + search_space = result.scalars().first() + if not search_space: + return _failed( + {"error": "Search space not found"}, + error="Search space not found", + ) - config_id = ( - search_space.image_generation_config_id or IMAGE_GEN_AUTO_MODE_ID - ) + config_id = ( + search_space.image_generation_config_id + or IMAGE_GEN_AUTO_MODE_ID + ) # Build generation kwargs # NOTE: size, quality, and style are intentionally NOT passed. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py index 5f76f1d52..ddfcbd7fb 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py @@ -51,5 +51,8 @@ def load_tools( create_generate_image_tool( search_space_id=d["search_space_id"], db_session=d["db_session"], + image_generation_config_id_override=d.get( + "image_generation_config_id_override" + ), ), ] diff --git a/surfsense_backend/app/automations/actions/agent_task/dependencies.py b/surfsense_backend/app/automations/actions/agent_task/dependencies.py index 79107cd65..e3736cc95 100644 --- a/surfsense_backend/app/automations/actions/agent_task/dependencies.py +++ b/surfsense_backend/app/automations/actions/agent_task/dependencies.py @@ -8,6 +8,12 @@ from typing import Any from langgraph.checkpoint.memory import InMemorySaver from sqlalchemy.ext.asyncio import AsyncSession +from app.automations.services.model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + assert_models_billable, +) +from app.db import SearchSpace from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( setup_connector_and_firecrawl, @@ -33,17 +39,48 @@ async def build_dependencies( *, session: AsyncSession, search_space_id: int, + agent_llm_id: int | None = None, + image_generation_config_id: int | None = None, + vision_llm_config_id: int | None = None, ) -> AgentDependencies: """Load the LLM bundle, connector service, and a per-invoke in-memory checkpointer. - Uses the search space's default LLM config (``config_id=-1``). Per-step - model overrides land in a future iteration alongside the ``model`` param. + Resolves the agent LLM from the automation's *captured* model snapshot + (``agent_llm_id``) so runs are insulated from later chat/search-space model + changes. The model policy is enforced here as a runtime backstop: a captured + model that is no longer billable (e.g. a premium global config was removed) + fails the run clearly instead of silently consuming a free model. + + When ``agent_llm_id`` is ``None`` (no captured snapshot — defensive fallback), + fall back to the live search space's ``agent_llm_id`` and validate that. """ + if agent_llm_id is not None: + try: + assert_models_billable( + agent_llm_id=agent_llm_id, + image_generation_config_id=image_generation_config_id, + vision_llm_config_id=vision_llm_config_id, + ) + except AutomationModelPolicyError as exc: + raise DependencyError(str(exc)) from exc + resolved_agent_llm_id = agent_llm_id or 0 + else: + search_space = await session.get(SearchSpace, search_space_id) + if search_space is None: + raise DependencyError(f"search space {search_space_id} not found") + try: + assert_automation_models_billable(search_space) + except AutomationModelPolicyError as exc: + raise DependencyError(str(exc)) from exc + resolved_agent_llm_id = search_space.agent_llm_id or 0 + llm, agent_config, err = await load_llm_bundle( - session, config_id=-1, search_space_id=search_space_id + session, + config_id=resolved_agent_llm_id, + search_space_id=search_space_id, ) if err is not None or llm is None: - raise DependencyError(err or "failed to load default LLM config") + raise DependencyError(err or "failed to load agent LLM config") connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( session, search_space_id=search_space_id diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index fa02d263f..b645b748d 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -147,6 +147,9 @@ async def run_agent_task( deps = await build_dependencies( session=agent_session, search_space_id=ctx.search_space_id, + agent_llm_id=ctx.agent_llm_id, + image_generation_config_id=ctx.image_generation_config_id, + vision_llm_config_id=ctx.vision_llm_config_id, ) agent = await create_multi_agent_chat_deep_agent( @@ -161,6 +164,7 @@ async def run_agent_task( firecrawl_api_key=deps.firecrawl_api_key, thread_visibility=ChatVisibility.PRIVATE, mentioned_document_ids=mentioned_document_ids, + image_generation_config_id=ctx.image_generation_config_id, ) agent_query, runtime_context = await _resolve_mention_context( diff --git a/surfsense_backend/app/automations/actions/types.py b/surfsense_backend/app/automations/actions/types.py index 2c4ffad8d..453721a43 100644 --- a/surfsense_backend/app/automations/actions/types.py +++ b/surfsense_backend/app/automations/actions/types.py @@ -20,6 +20,12 @@ class ActionContext: step_id: str search_space_id: int creator_user_id: UUID | None + # Captured model snapshot from the automation definition (``definition.models``), + # resolved per run instead of the live search space. ``None`` falls back to the + # search space's current prefs (defensive; should not happen post-capture). + agent_llm_id: int | None = None + image_generation_config_id: int | None = None + vision_llm_config_id: int | None = None ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py index b67f0af09..911ae57a6 100644 --- a/surfsense_backend/app/automations/api/automation.py +++ b/surfsense_backend/app/automations/api/automation.py @@ -3,6 +3,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, Query, status +from pydantic import BaseModel from app.automations.schemas.api import ( AutomationCreate, @@ -16,6 +17,17 @@ from app.automations.services import AutomationService, get_automation_service router = APIRouter() +class ModelEligibilityViolation(BaseModel): + kind: str + config_id: int | None + reason: str + + +class ModelEligibility(BaseModel): + allowed: bool + violations: list[ModelEligibilityViolation] + + @router.post( "/automations", response_model=AutomationDetail, @@ -47,6 +59,23 @@ async def list_automations( ) +@router.get("/automations/model-eligibility", response_model=ModelEligibility) +async def get_automation_model_eligibility( + search_space_id: int = Query(...), + service: AutomationService = Depends(get_automation_service), +) -> ModelEligibility: + """Report whether a search space's models are billable for automations. + + Used by the frontend to gate creation: automations may only use premium + global models or user BYOK models (free models and Auto mode are blocked). + + NOTE: declared before ``/automations/{automation_id}`` so the literal path + isn't captured by the int-typed ``{automation_id}`` route. + """ + result = await service.model_eligibility(search_space_id=search_space_id) + return ModelEligibility.model_validate(result) + + @router.get("/automations/{automation_id}", response_model=AutomationDetail) async def get_automation( automation_id: int, diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index 6a33ab314..da249d8e5 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -9,7 +9,10 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.automations.actions.types import ActionContext from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun -from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.envelope import ( + AutomationDefinition, + AutomationModels, +) from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context @@ -47,7 +50,7 @@ async def execute_run(session: AsyncSession, run_id: int) -> None: for step in definition.plan: template_ctx = _build_template_ctx(run, step_outputs) - action_ctx = _build_action_ctx(session, run, step) + action_ctx = _build_action_ctx(session, run, step, definition.models) result = await execute_step( step=step, template_context=template_ctx, @@ -82,7 +85,7 @@ async def _run_on_failure( return template_ctx = _build_template_ctx(run, step_outputs={}) for step in definition.execution.on_failure: - action_ctx = _build_action_ctx(session, run, step) + action_ctx = _build_action_ctx(session, run, step, definition.models) result = await execute_step( step=step, template_context=template_ctx, @@ -117,7 +120,10 @@ def _build_template_ctx( def _build_action_ctx( - session: AsyncSession, run: AutomationRun, step: PlanStep + session: AsyncSession, + run: AutomationRun, + step: PlanStep, + models: AutomationModels | None, ) -> ActionContext: automation = run.automation return ActionContext( @@ -126,4 +132,9 @@ def _build_action_ctx( step_id=step.step_id, search_space_id=automation.search_space_id, creator_user_id=automation.created_by_user_id, + agent_llm_id=models.agent_llm_id if models else None, + image_generation_config_id=( + models.image_generation_config_id if models else None + ), + vision_llm_config_id=models.vision_llm_config_id if models else None, ) diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 3fb0a739b..72404264e 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .envelope import AutomationDefinition +from .envelope import AutomationDefinition, AutomationModels from .execution import Execution from .inputs import Inputs from .metadata import Metadata @@ -11,6 +11,7 @@ from .trigger_spec import TriggerSpec __all__ = [ "AutomationDefinition", + "AutomationModels", "Execution", "Inputs", "Metadata", diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index f919b2abb..7ca55b1ce 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -11,6 +11,21 @@ from .plan_step import PlanStep from .trigger_spec import TriggerSpec +class AutomationModels(BaseModel): + """Captured model profile for an automation. + + Snapshotted from the search space's preferences at create time so runs are + insulated from later chat/search-space model changes. Config-id conventions + match the shared scheme (``0`` Auto, ``< 0`` global, ``> 0`` BYOK). + """ + + model_config = ConfigDict(extra="forbid") + + agent_llm_id: int = 0 + image_generation_config_id: int = 0 + vision_llm_config_id: int = 0 + + class AutomationDefinition(BaseModel): """Top-level shape of an automation.""" @@ -24,3 +39,7 @@ class AutomationDefinition(BaseModel): plan: list[PlanStep] = Field(..., min_length=1) execution: Execution = Field(default_factory=Execution) metadata: Metadata = Field(default_factory=Metadata) + # Captured server-side at create() and preserved across update(); resolved + # at runtime instead of the live search space. Optional so drafts/builder + # payloads validate without it. + models: AutomationModels | None = None diff --git a/surfsense_backend/app/automations/services/__init__.py b/surfsense_backend/app/automations/services/__init__.py index 597aca98a..904a3413a 100644 --- a/surfsense_backend/app/automations/services/__init__.py +++ b/surfsense_backend/app/automations/services/__init__.py @@ -3,14 +3,26 @@ from __future__ import annotations from .automation import AutomationService, get_automation_service +from .model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + assert_models_billable, + get_automation_model_eligibility, + get_model_eligibility, +) from .run import RunService, get_run_service from .trigger import TriggerService, get_trigger_service __all__ = [ + "AutomationModelPolicyError", "AutomationService", "RunService", "TriggerService", + "assert_automation_models_billable", + "assert_models_billable", + "get_automation_model_eligibility", "get_automation_service", + "get_model_eligibility", "get_run_service", "get_trigger_service", ] diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py index 0d2937e0e..6c602d886 100644 --- a/surfsense_backend/app/automations/services/automation.py +++ b/surfsense_backend/app/automations/services/automation.py @@ -18,9 +18,15 @@ from app.automations.schemas.api import ( AutomationUpdate, TriggerCreate, ) +from app.automations.schemas.definition.envelope import AutomationModels +from app.automations.services.model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + get_automation_model_eligibility, +) from app.automations.triggers import get_trigger from app.automations.triggers.schedule import compute_next_fire_at -from app.db import Permission, User, get_async_session +from app.db import Permission, SearchSpace, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission @@ -37,6 +43,16 @@ class AutomationService: await self._authorize( payload.search_space_id, Permission.AUTOMATIONS_CREATE.value ) + search_space = await self._assert_models_billable(payload.search_space_id) + + # Snapshot the search space's current (already-validated) model prefs onto + # the definition so runs are insulated from later chat/search-space model + # changes. Captured ids are guaranteed billable by the check above. + payload.definition.models = AutomationModels( + agent_llm_id=search_space.agent_llm_id or 0, + image_generation_config_id=search_space.image_generation_config_id or 0, + vision_llm_config_id=search_space.vision_llm_config_id or 0, + ) automation = Automation( search_space_id=payload.search_space_id, @@ -105,9 +121,15 @@ class AutomationService: if "status" in data: automation.status = data["status"] if "definition" in data: - automation.definition = patch.definition.model_dump( - mode="json", by_alias=True - ) + new_def = patch.definition.model_dump(mode="json", by_alias=True) + # Preserve the captured model snapshot across edits so a definition + # change never silently re-binds the automation to the current chat + # model selection. Backend-managed; survives whether or not the + # client round-trips ``models``. + existing_models = (automation.definition or {}).get("models") + if existing_models is not None: + new_def["models"] = existing_models + automation.definition = new_def automation.version += 1 await self.session.commit() @@ -143,6 +165,40 @@ class AutomationService: ) return automation + async def model_eligibility(self, *, search_space_id: int) -> dict: + """Return whether a search space's models are billable for automations. + + ``{"allowed": bool, "violations": [{kind, config_id, reason}, ...]}``. + """ + await self._authorize(search_space_id, Permission.AUTOMATIONS_READ.value) + search_space = await self.session.get(SearchSpace, search_space_id) + if search_space is None: + raise HTTPException( + status_code=404, detail=f"search space {search_space_id} not found" + ) + return get_automation_model_eligibility(search_space) + + async def _assert_models_billable(self, search_space_id: int) -> SearchSpace: + """Reject creation when the search space's models aren't billable. + + Automations may only use premium global models or user BYOK models; free + global models and Auto mode are blocked. Mirrors the runtime backstop in + ``agent_task`` so users can't save an automation that would fail to run. + + Returns the loaded :class:`SearchSpace` so the caller can capture its + model prefs without a second DB read. + """ + search_space = await self.session.get(SearchSpace, search_space_id) + if search_space is None: + raise HTTPException( + status_code=404, detail=f"search space {search_space_id} not found" + ) + try: + assert_automation_models_billable(search_space) + except AutomationModelPolicyError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + return search_space + async def _authorize(self, search_space_id: int, permission: str) -> None: await check_permission( self.session, diff --git a/surfsense_backend/app/automations/services/model_policy.py b/surfsense_backend/app/automations/services/model_policy.py new file mode 100644 index 000000000..88e9d5f28 --- /dev/null +++ b/surfsense_backend/app/automations/services/model_policy.py @@ -0,0 +1,173 @@ +"""Model-billing policy for automations. + +Automations run unattended, so every run must be **billable**: it may only use +either a premium global model (``billing_tier == "premium"``) or a user-provided +BYOK model (a positive config id pointing at a per-user/per-space DB row). Free +global models and Auto mode are blocked, because Auto can dispatch to a free +deployment and free models aren't metered in premium credits. + +Config id conventions (shared across chat / image / vision): +- ``id == 0`` → Auto mode (``AUTO_MODE_ID`` / ``IMAGE_GEN_AUTO_MODE_ID`` / + ``VISION_AUTO_MODE_ID``). Blocked. +- ``id < 0`` → global YAML/OpenRouter config. Allowed only if premium. +- ``id > 0`` → user BYOK DB row. Always allowed. + +This module is the single source of truth used by both creation-time enforcement +(``AutomationService.create`` and the ``create_automation`` chat tool) and the +runtime backstop (``agent_task`` dependencies). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal + +if TYPE_CHECKING: + from app.db import SearchSpace + +ModelKind = Literal["llm", "image", "vision"] + +_KIND_LABEL: dict[ModelKind, str] = { + "llm": "agent LLM", + "image": "image generation model", + "vision": "vision model", +} + + +def _is_premium_global(kind: ModelKind, config_id: int) -> bool: + """Return True if a negative (global) config id is a premium tier model.""" + from app.config import config as app_config + + cfg: dict | None = None + if kind == "llm": + from app.agents.new_chat.llm_config import load_global_llm_config_by_id + + cfg = load_global_llm_config_by_id(config_id) + elif kind == "image": + cfg = next( + ( + c + for c in app_config.GLOBAL_IMAGE_GEN_CONFIGS + if c.get("id") == config_id + ), + None, + ) + else: # vision + cfg = next( + ( + c + for c in app_config.GLOBAL_VISION_LLM_CONFIGS + if c.get("id") == config_id + ), + None, + ) + + if not cfg: + return False + return str(cfg.get("billing_tier", "free")).lower() == "premium" + + +def _classify(kind: ModelKind, config_id: int | None) -> tuple[bool, str]: + """Classify a resolved config id as allowed or blocked. + + Returns ``(allowed, reason)``; ``reason`` is empty when allowed. + """ + label = _KIND_LABEL[kind] + + if config_id is None or config_id == 0: + return ( + False, + f"The {label} is set to Auto mode. Automations require an explicit " + "premium model or your own (BYOK) model so every run is billable.", + ) + + if config_id > 0: + # Positive id → user-owned BYOK config. Always allowed. + return True, "" + + # Negative id → global config. Allowed only if premium. + if _is_premium_global(kind, config_id): + return True, "" + + return ( + False, + f"The {label} is a free model. Automations can only use premium models " + "or your own (BYOK) models so every run is billable.", + ) + + +def get_model_eligibility( + *, + agent_llm_id: int | None, + image_generation_config_id: int | None, + vision_llm_config_id: int | None, +) -> dict: + """Return ``{"allowed": bool, "violations": [...]}`` for explicit config ids. + + The ID-based core shared by both the search-space path (creation/eligibility) + and the captured-snapshot path (runtime backstop). Each violation is + ``{"kind", "config_id", "reason"}``. + """ + checks: list[tuple[ModelKind, int | None]] = [ + ("llm", agent_llm_id), + ("image", image_generation_config_id), + ("vision", vision_llm_config_id), + ] + + violations: list[dict] = [] + for kind, config_id in checks: + allowed, reason = _classify(kind, config_id) + if not allowed: + violations.append({"kind": kind, "config_id": config_id, "reason": reason}) + + return {"allowed": not violations, "violations": violations} + + +def get_automation_model_eligibility(search_space: SearchSpace) -> dict: + """Return ``{"allowed": bool, "violations": [...]}`` for a search space. + + Used by the eligibility endpoint and the chat tool's early check. Thin + wrapper over :func:`get_model_eligibility`. + """ + return get_model_eligibility( + agent_llm_id=search_space.agent_llm_id, + image_generation_config_id=search_space.image_generation_config_id, + vision_llm_config_id=search_space.vision_llm_config_id, + ) + + +class AutomationModelPolicyError(Exception): + """Raised when a search space's models are not billable for automations.""" + + def __init__(self, violations: list[dict]) -> None: + self.violations = violations + reasons = "; ".join(v["reason"] for v in violations) + super().__init__( + reasons or "Automations require premium or BYOK models for all model slots." + ) + + +def assert_models_billable( + *, + agent_llm_id: int | None, + image_generation_config_id: int | None, + vision_llm_config_id: int | None, +) -> None: + """Raise :class:`AutomationModelPolicyError` if any explicit id is not billable. + + The ID-based core used by the runtime backstop against an automation's + captured model snapshot. + """ + result = get_model_eligibility( + agent_llm_id=agent_llm_id, + image_generation_config_id=image_generation_config_id, + vision_llm_config_id=vision_llm_config_id, + ) + if not result["allowed"]: + raise AutomationModelPolicyError(result["violations"]) + + +def assert_automation_models_billable(search_space: SearchSpace) -> None: + """Raise :class:`AutomationModelPolicyError` if any model slot is not billable.""" + result = get_automation_model_eligibility(search_space) + if not result["allowed"]: + raise AutomationModelPolicyError(result["violations"]) diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py new file mode 100644 index 000000000..ac20b2608 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py @@ -0,0 +1,174 @@ +"""Lock the runtime model-policy backstop in ``build_dependencies``. + +Automations resolve their LLM from the *captured* ``agent_llm_id`` snapshot (so +runs are insulated from later chat/search-space model changes), and the model +policy is re-checked at run time so a captured model that is no longer billable +fails the run clearly. When no snapshot is present, resolution falls back to the +live search space. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +import pytest + +import app.automations.actions.agent_task.dependencies as deps_mod +from app.automations.actions.agent_task.dependencies import ( + DependencyError, + build_dependencies, +) +from app.automations.services.model_policy import AutomationModelPolicyError + +pytestmark = pytest.mark.unit + + +class _FakeSession: + """Minimal async session whose ``get`` returns a preset search space.""" + + def __init__(self, search_space: Any) -> None: + self._search_space = search_space + + async def get(self, _model: Any, _pk: int) -> Any: + return self._search_space + + +@pytest.fixture +def patched_side_effects(monkeypatch: pytest.MonkeyPatch): + """Stub the connector setup + checkpointer so only policy/LLM logic runs.""" + + async def _fake_setup(_session, *, search_space_id): + return (SimpleNamespace(name="connector"), "fc-key") + + monkeypatch.setattr(deps_mod, "setup_connector_and_firecrawl", _fake_setup) + return None + + +async def test_build_dependencies_resolves_captured_agent_llm_id( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """The bundle loads with the *captured* ``agent_llm_id``, not the live search space.""" + captured: dict[str, Any] = {} + + async def _fake_load(_session, *, config_id, search_space_id): + captured["config_id"] = config_id + captured["search_space_id"] = search_space_id + return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) + + monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) + # Captured path validates the explicit ids; passes for this test. + monkeypatch.setattr(deps_mod, "assert_models_billable", lambda **_kw: None) + # A different value on the live search space proves we ignore it when a + # snapshot is supplied. + monkeypatch.setattr( + deps_mod, + "assert_automation_models_billable", + lambda _ss: pytest.fail("search-space policy should not run on captured path"), + ) + + search_space = SimpleNamespace(agent_llm_id=-99) + result = await build_dependencies( + session=_FakeSession(search_space), + search_space_id=42, + agent_llm_id=-7, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + + assert captured == {"config_id": -7, "search_space_id": 42} + assert result.llm.name == "llm" + assert result.firecrawl_api_key == "fc-key" + + +async def test_build_dependencies_validates_captured_ids( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """The captured ids (not the search space) are what gets policy-checked.""" + seen: dict[str, Any] = {} + + def _capture(**kwargs): + seen.update(kwargs) + + monkeypatch.setattr(deps_mod, "assert_models_billable", _capture) + + async def _fake_load(_session, *, config_id, search_space_id): + return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) + + monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) + + await build_dependencies( + session=_FakeSession(SimpleNamespace(agent_llm_id=0)), + search_space_id=42, + agent_llm_id=-7, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + + assert seen == { + "agent_llm_id": -7, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + + +async def test_build_dependencies_raises_on_captured_policy_violation( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """A blocked captured model raises ``DependencyError`` so the step fails clearly.""" + + def _raise(**_kw): + raise AutomationModelPolicyError( + [{"kind": "image", "config_id": -2, "reason": "free model"}] + ) + + monkeypatch.setattr(deps_mod, "assert_models_billable", _raise) + monkeypatch.setattr( + deps_mod, + "load_llm_bundle", + lambda *a, **k: pytest.fail("load_llm_bundle should not be called"), + ) + + with pytest.raises(DependencyError): + await build_dependencies( + session=_FakeSession(SimpleNamespace(agent_llm_id=-7)), + search_space_id=42, + agent_llm_id=-7, + image_generation_config_id=-2, + vision_llm_config_id=-1, + ) + + +async def test_build_dependencies_falls_back_to_search_space( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """With no captured snapshot, resolve + validate the live search space.""" + captured: dict[str, Any] = {} + + async def _fake_load(_session, *, config_id, search_space_id): + captured["config_id"] = config_id + return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) + + monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) + monkeypatch.setattr(deps_mod, "assert_automation_models_billable", lambda _ss: None) + monkeypatch.setattr( + deps_mod, + "assert_models_billable", + lambda **_kw: pytest.fail("captured policy should not run on fallback path"), + ) + + search_space = SimpleNamespace(agent_llm_id=-7) + result = await build_dependencies( + session=_FakeSession(search_space), search_space_id=42 + ) + + assert captured == {"config_id": -7} + assert result.llm.name == "llm" + + +async def test_build_dependencies_raises_when_search_space_missing( + patched_side_effects, +) -> None: + """A missing search space (fallback path) surfaces as a ``DependencyError``.""" + with pytest.raises(DependencyError): + await build_dependencies(session=_FakeSession(None), search_space_id=999) diff --git a/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py b/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py new file mode 100644 index 000000000..d7e3c4a0c --- /dev/null +++ b/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py @@ -0,0 +1,59 @@ +"""Lock that the executor propagates the captured model snapshot into the +``ActionContext``, so runs resolve their own model (insulated from chat / +search-space changes) and not the live search space. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import cast + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.runtime.executor import _build_action_ctx +from app.automations.schemas.definition.envelope import AutomationModels +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def _run() -> SimpleNamespace: + return SimpleNamespace( + id=1, + automation=SimpleNamespace(search_space_id=42, created_by_user_id="u-1"), + ) + + +def test_build_action_ctx_propagates_captured_models() -> None: + """``definition.models`` flows onto the ActionContext model fields.""" + models = AutomationModels( + agent_llm_id=-1, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + ctx = _build_action_ctx( + cast(AsyncSession, None), + _run(), + PlanStep(step_id="s1", action="agent_task"), + models, + ) + + assert ctx.search_space_id == 42 + assert ctx.agent_llm_id == -1 + assert ctx.image_generation_config_id == 5 + assert ctx.vision_llm_config_id == -1 + + +def test_build_action_ctx_none_models_leaves_fields_none() -> None: + """No captured snapshot → model fields are None (defensive fallback path).""" + ctx = _build_action_ctx( + cast(AsyncSession, None), + _run(), + PlanStep(step_id="s1", action="agent_task"), + None, + ) + + assert ctx.agent_llm_id is None + assert ctx.image_generation_config_id is None + assert ctx.vision_llm_config_id is None diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py index d7b392a1d..25e193ffa 100644 --- a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py @@ -5,7 +5,10 @@ from __future__ import annotations import pytest from pydantic import ValidationError -from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.envelope import ( + AutomationDefinition, + AutomationModels, +) from app.automations.schemas.definition.plan_step import PlanStep pytestmark = pytest.mark.unit @@ -27,6 +30,34 @@ def test_automation_definition_accepts_minimal_valid_input_with_sensible_default assert definition.goal is None assert definition.inputs is None assert definition.triggers == [] + # ``models`` is optional (populated server-side at create()). + assert definition.models is None + + +def test_automation_definition_models_round_trip() -> None: + """The captured ``models`` snapshot survives a model_dump/validate round-trip.""" + definition = AutomationDefinition( + name="Daily digest", + plan=[PlanStep(step_id="s1", action="agent_task")], + models=AutomationModels( + agent_llm_id=-1, + image_generation_config_id=5, + vision_llm_config_id=-1, + ), + ) + + dumped = definition.model_dump(mode="json", by_alias=True) + assert dumped["models"] == { + "agent_llm_id": -1, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + + restored = AutomationDefinition.model_validate(dumped) + assert restored.models is not None + assert restored.models.agent_llm_id == -1 + assert restored.models.image_generation_config_id == 5 + assert restored.models.vision_llm_config_id == -1 def test_automation_definition_rejects_unknown_top_level_field() -> None: diff --git a/surfsense_backend/tests/unit/automations/services/__init__.py b/surfsense_backend/tests/unit/automations/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py b/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py new file mode 100644 index 000000000..d81302380 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py @@ -0,0 +1,236 @@ +"""Lock creation-time model-policy enforcement in ``AutomationService``. + +Creation (REST + manual builder) rejects search spaces whose models aren't +billable for automations with HTTP 422, mirroring the runtime backstop. These +tests isolate the new ``_assert_models_billable`` / ``model_eligibility`` paths +without touching the DB commit. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +import pytest +from fastapi import HTTPException + +import app.automations.services.automation as automation_mod +from app.automations.schemas.api import AutomationCreate, AutomationUpdate +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.plan_step import PlanStep +from app.automations.services.automation import AutomationService +from app.automations.services.model_policy import AutomationModelPolicyError + +pytestmark = pytest.mark.unit + + +class _FakeSession: + def __init__(self, search_space: Any) -> None: + self._search_space = search_space + self.added: list[Any] = [] + self.commits = 0 + + async def get(self, _model: Any, _pk: int) -> Any: + return self._search_space + + def add(self, obj: Any) -> None: + self.added.append(obj) + + async def commit(self) -> None: + self.commits += 1 + + +def _service(search_space: Any) -> AutomationService: + return AutomationService( + session=_FakeSession(search_space), user=SimpleNamespace(id="u-1") + ) + + +def _definition(**kwargs: Any) -> AutomationDefinition: + return AutomationDefinition( + name="A", + plan=[PlanStep(step_id="s1", action="agent_task")], + **kwargs, + ) + + +async def test_assert_models_billable_raises_422_on_violation( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A blocked model maps the policy error to HTTP 422.""" + + def _raise(_ss): + raise AutomationModelPolicyError( + [{"kind": "llm", "config_id": 0, "reason": "Auto mode"}] + ) + + monkeypatch.setattr(automation_mod, "assert_automation_models_billable", _raise) + + service = _service(SimpleNamespace(agent_llm_id=0)) + with pytest.raises(HTTPException) as exc_info: + await service._assert_models_billable(1) + + assert exc_info.value.status_code == 422 + + +async def test_assert_models_billable_raises_404_when_missing( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A missing search space is a 404, not a policy error.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + service = _service(None) + with pytest.raises(HTTPException) as exc_info: + await service._assert_models_billable(999) + + assert exc_info.value.status_code == 404 + + +async def test_assert_models_billable_returns_search_space_when_ok( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """When the policy accepts, the loaded search space is returned for reuse.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + search_space = SimpleNamespace(agent_llm_id=-1) + service = _service(search_space) + assert await service._assert_models_billable(1) is search_space + + +async def test_create_injects_captured_models_from_search_space( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """create() snapshots the search space's model prefs onto the definition.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + async def _noop_authorize(self, *_a, **_k): + return None + + monkeypatch.setattr(AutomationService, "_authorize", _noop_authorize) + + async def _return_added(self, _aid): + return self.session.added[-1] + + monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added) + + search_space = SimpleNamespace( + agent_llm_id=-1, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + service = _service(search_space) + payload = AutomationCreate( + search_space_id=1, + name="A", + definition=_definition(), + ) + + automation = await service.create(payload) + + assert automation.definition["models"] == { + "agent_llm_id": -1, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + + +async def test_create_treats_unset_prefs_as_auto_zero( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """``None`` search-space prefs are captured as ``0`` (Auto) ids.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + async def _noop_authorize(self, *_a, **_k): + return None + + monkeypatch.setattr(AutomationService, "_authorize", _noop_authorize) + + async def _return_added(self, _aid): + return self.session.added[-1] + + monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added) + + search_space = SimpleNamespace( + agent_llm_id=None, + image_generation_config_id=None, + vision_llm_config_id=None, + ) + service = _service(search_space) + payload = AutomationCreate(search_space_id=1, name="A", definition=_definition()) + + automation = await service.create(payload) + + assert automation.definition["models"] == { + "agent_llm_id": 0, + "image_generation_config_id": 0, + "vision_llm_config_id": 0, + } + + +async def test_update_preserves_captured_models( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A definition edit carries over the previously captured ``models``.""" + captured = { + "agent_llm_id": -1, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + existing = SimpleNamespace( + search_space_id=1, + definition={"name": "A", "plan": [], "models": captured}, + version=3, + ) + + async def _noop_authorize(self, *_a, **_k): + return None + + async def _return_existing(self, _aid): + return existing + + monkeypatch.setattr(AutomationService, "_authorize", _noop_authorize) + monkeypatch.setattr( + AutomationService, "_get_with_triggers_or_raise", _return_existing + ) + + service = _service(SimpleNamespace()) + # The incoming patch definition has no ``models`` (frontend strips it). + patch = AutomationUpdate(definition=_definition()) + + result = await service.update(7, patch) + + assert result.definition["models"] == captured + assert result.version == 4 + + +async def test_model_eligibility_authorizes_and_returns_payload( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """``model_eligibility`` checks read access then returns the eligibility dict.""" + authorized: dict[str, Any] = {} + + async def _fake_check_permission(_session, _user, ss_id, permission, _msg): + authorized["search_space_id"] = ss_id + authorized["permission"] = permission + + monkeypatch.setattr(automation_mod, "check_permission", _fake_check_permission) + monkeypatch.setattr( + automation_mod, + "get_automation_model_eligibility", + lambda _ss: {"allowed": False, "violations": [{"kind": "image"}]}, + ) + + service = _service(SimpleNamespace(agent_llm_id=-2)) + result = await service.model_eligibility(search_space_id=5) + + assert result == {"allowed": False, "violations": [{"kind": "image"}]} + assert authorized["search_space_id"] == 5 + assert authorized["permission"] == "automations:read" diff --git a/surfsense_backend/tests/unit/automations/services/test_model_policy.py b/surfsense_backend/tests/unit/automations/services/test_model_policy.py new file mode 100644 index 000000000..2a471b4e9 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/services/test_model_policy.py @@ -0,0 +1,196 @@ +"""Lock the automation model-billing policy. + +Automations may only run on billable models: premium global configs +(``billing_tier == "premium"``) or user BYOK configs (positive id). Free +globals and Auto mode (id == 0 / None) are blocked. These tests pin that rule +across all three model slots (chat LLM, image, vision). +""" + +from __future__ import annotations + +from types import SimpleNamespace + +import pytest + +import app.automations.services.model_policy as model_policy +from app.automations.services.model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + assert_models_billable, + get_automation_model_eligibility, + get_model_eligibility, +) + +pytestmark = pytest.mark.unit + + +def _search_space(*, llm: int | None, image: int | None, vision: int | None): + """Minimal stand-in for the ``SearchSpace`` ORM row the policy reads.""" + return SimpleNamespace( + agent_llm_id=llm, + image_generation_config_id=image, + vision_llm_config_id=vision, + ) + + +@pytest.fixture +def patched_globals(monkeypatch: pytest.MonkeyPatch): + """Stub the global config sources the policy consults for negative ids. + + Negative ids: -1 is premium, -2 is free, for each of llm/image/vision. + """ + llm_configs = { + -1: {"id": -1, "billing_tier": "premium"}, + -2: {"id": -2, "billing_tier": "free"}, + } + monkeypatch.setattr( + "app.agents.new_chat.llm_config.load_global_llm_config_by_id", + lambda cid: llm_configs.get(cid), + ) + + from app.config import config as app_config + + monkeypatch.setattr( + app_config, + "GLOBAL_IMAGE_GEN_CONFIGS", + [ + {"id": -1, "billing_tier": "premium"}, + {"id": -2, "billing_tier": "free"}, + ], + raising=False, + ) + monkeypatch.setattr( + app_config, + "GLOBAL_VISION_LLM_CONFIGS", + [ + {"id": -1, "billing_tier": "premium"}, + {"id": -2, "billing_tier": "free"}, + ], + raising=False, + ) + return None + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_byok_positive_id_is_allowed(kind: str, patched_globals) -> None: + """A positive config id is a user-owned BYOK model — always billable.""" + allowed, reason = model_policy._classify(kind, 7) + assert allowed is True + assert reason == "" + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +@pytest.mark.parametrize("config_id", [0, None]) +def test_auto_mode_is_blocked(kind: str, config_id, patched_globals) -> None: + """Auto mode (id 0) and an unset slot (None) are blocked.""" + allowed, reason = model_policy._classify(kind, config_id) + assert allowed is False + assert "Auto mode" in reason + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_premium_global_is_allowed(kind: str, patched_globals) -> None: + """A negative (global) id with premium billing tier is allowed.""" + allowed, reason = model_policy._classify(kind, -1) + assert allowed is True + assert reason == "" + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_free_global_is_blocked(kind: str, patched_globals) -> None: + """A negative (global) id with a free billing tier is blocked.""" + allowed, reason = model_policy._classify(kind, -2) + assert allowed is False + assert "free model" in reason + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_unknown_global_id_is_blocked(kind: str, patched_globals) -> None: + """A negative id that resolves to no config is treated as not premium.""" + allowed, _ = model_policy._classify(kind, -999) + assert allowed is False + + +def test_eligibility_all_billable(patched_globals) -> None: + """Premium LLM + BYOK image + premium vision → allowed, no violations.""" + search_space = _search_space(llm=-1, image=5, vision=-1) + result = get_automation_model_eligibility(search_space) + assert result == {"allowed": True, "violations": []} + + +def test_eligibility_reports_each_violation(patched_globals) -> None: + """A free LLM, Auto image, and free vision each produce a violation.""" + search_space = _search_space(llm=-2, image=0, vision=-2) + result = get_automation_model_eligibility(search_space) + + assert result["allowed"] is False + kinds = {v["kind"] for v in result["violations"]} + assert kinds == {"llm", "image", "vision"} + # config_id is echoed back for the UI / settings deep-link. + by_kind = {v["kind"]: v["config_id"] for v in result["violations"]} + assert by_kind == {"llm": -2, "image": 0, "vision": -2} + + +def test_assert_raises_with_violations(patched_globals) -> None: + """``assert_automation_models_billable`` raises when any slot is blocked.""" + search_space = _search_space(llm=0, image=5, vision=-1) + with pytest.raises(AutomationModelPolicyError) as exc_info: + assert_automation_models_billable(search_space) + + assert len(exc_info.value.violations) == 1 + assert exc_info.value.violations[0]["kind"] == "llm" + + +def test_assert_passes_when_all_billable(patched_globals) -> None: + """No exception when every slot is premium or BYOK.""" + search_space = _search_space(llm=3, image=-1, vision=4) + assert assert_automation_models_billable(search_space) is None + + +# --- ID-based core (used by the runtime backstop against captured snapshots) --- + + +def test_get_model_eligibility_all_billable(patched_globals) -> None: + """Premium LLM + BYOK image + premium vision (explicit ids) → allowed.""" + result = get_model_eligibility( + agent_llm_id=-1, image_generation_config_id=5, vision_llm_config_id=-1 + ) + assert result == {"allowed": True, "violations": []} + + +def test_get_model_eligibility_reports_each_violation(patched_globals) -> None: + """Free LLM, Auto image, free vision (explicit ids) each produce a violation.""" + result = get_model_eligibility( + agent_llm_id=-2, image_generation_config_id=0, vision_llm_config_id=-2 + ) + assert result["allowed"] is False + by_kind = {v["kind"]: v["config_id"] for v in result["violations"]} + assert by_kind == {"llm": -2, "image": 0, "vision": -2} + + +def test_assert_models_billable_raises(patched_globals) -> None: + """``assert_models_billable`` raises when any explicit id is blocked.""" + with pytest.raises(AutomationModelPolicyError) as exc_info: + assert_models_billable( + agent_llm_id=0, image_generation_config_id=5, vision_llm_config_id=-1 + ) + assert len(exc_info.value.violations) == 1 + assert exc_info.value.violations[0]["kind"] == "llm" + + +def test_assert_models_billable_passes(patched_globals) -> None: + """No exception when every explicit id is premium or BYOK.""" + assert ( + assert_models_billable( + agent_llm_id=3, image_generation_config_id=-1, vision_llm_config_id=4 + ) + is None + ) + + +def test_search_space_wrapper_delegates_to_core(patched_globals) -> None: + """The search-space wrapper produces the same result as the ID core.""" + search_space = _search_space(llm=-2, image=0, vision=-2) + assert get_automation_model_eligibility(search_space) == get_model_eligibility( + agent_llm_id=-2, image_generation_config_id=0, vision_llm_config_id=-2 + ) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx index 756221d38..6bbe55ec9 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx @@ -1,6 +1,8 @@ "use client"; import { ShieldAlert } from "lucide-react"; +import { useAutomationModelEligibility } from "@/hooks/use-automation-model-eligibility"; import { useAutomations } from "@/hooks/use-automations"; +import { AutomationModelGateAlert } from "./components/automation-model-gate-alert"; import { AutomationsEmptyState } from "./components/automations-empty-state"; import { AutomationsHeader } from "./components/automations-header"; import { AutomationsTable } from "./components/automations-table"; @@ -22,6 +24,18 @@ interface AutomationsContentProps { export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { const { automations, total, loading, error } = useAutomations(); const perms = useAutomationPermissions(); + // Gate creation on billable models (premium/BYOK). Only meaningful for + // users who can create; the eligibility query loads in parallel. + const { data: eligibility, isLoading: eligibilityLoading } = useAutomationModelEligibility( + perms.canCreate ? searchSpaceId : undefined + ); + const modelViolations = eligibility?.violations ?? []; + // Disable create CTAs while loading (avoid a flash of enabled buttons) and + // when the resolved models aren't billable. + const createDisabled = perms.canCreate && (eligibilityLoading || modelViolations.length > 0); + const disabledReason = eligibilityLoading + ? "Checking model eligibility…" + : modelViolations[0]?.reason; if (perms.loading) { // Permissions gate the entire page; defer everything until we know. @@ -77,7 +91,11 @@ export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { canCreate={perms.canCreate} showCreateCta={false} /> - + ); } @@ -89,7 +107,12 @@ export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { total={total} loading={loading} canCreate={perms.canCreate} + createDisabled={createDisabled} + disabledReason={disabledReason} /> + {modelViolations.length > 0 && ( + + )} = { + llm: "Agent model", + image: "Image model", + vision: "Vision model", +}; + +/** + * Warns that the search space's models aren't billable for automations. + * + * Automations may only use premium global models or your own (BYOK) models — + * free models and Auto mode are blocked so every run is metered in premium + * credits. Surfaced wherever a user can start creating an automation. + */ +export function AutomationModelGateAlert({ + searchSpaceId, + violations, + className, +}: AutomationModelGateAlertProps) { + if (violations.length === 0) return null; + + return ( + + + Automations need a premium or your own model + +

+ Automations run unattended, so every run must use a premium model or your own (BYOK) + model. Update these in your model settings, then create your automation. +

+
    + {violations.map((violation) => ( +
  • + + {KIND_LABEL[violation.kind]} + + — {violation.reason} +
  • + ))} +
+
+
+ ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx index cc54c5e94..ee7dadce6 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -2,10 +2,14 @@ import { MessageSquarePlus, SquarePen, Workflow } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; +import type { ModelEligibilityViolation } from "@/contracts/types/automation.types"; +import { AutomationModelGateAlert } from "./automation-model-gate-alert"; interface AutomationsEmptyStateProps { searchSpaceId: number; canCreate: boolean; + /** Model slots that block creation (free/Auto). Empty when eligible. */ + modelViolations?: ModelEligibilityViolation[]; } /** @@ -14,7 +18,13 @@ interface AutomationsEmptyStateProps { * "new automation" form. We surface the chat path explicitly so users * don't go hunting for an "add" button that doesn't exist. */ -export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsEmptyStateProps) { +export function AutomationsEmptyState({ + searchSpaceId, + canCreate, + modelViolations = [], +}: AutomationsEmptyStateProps) { + const modelsBlocked = modelViolations.length > 0; + return (
@@ -26,20 +36,26 @@ export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsE SurfSense drafts the automation for your approval.

{canCreate ? ( -
- - -
+ modelsBlocked ? ( +
+ +
+ ) : ( +
+ + +
+ ) ) : (

You don't have permission to create automations in this search space. diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index 8d5fab033..308eaccfb 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -1,7 +1,9 @@ "use client"; import { MessageSquarePlus, SquarePen } from "lucide-react"; import Link from "next/link"; +import type { ReactNode } from "react"; import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; interface AutomationsHeaderProps { searchSpaceId: number; @@ -14,8 +16,18 @@ interface AutomationsHeaderProps { * there to avoid a duplicate button. */ showCreateCta?: boolean; + /** + * Disable the create CTAs when the search space's models aren't billable + * for automations (free/Auto). When set, a tooltip explains why and the + * buttons render disabled rather than as links. + */ + createDisabled?: boolean; + disabledReason?: string; } +const DEFAULT_DISABLED_REASON = + "Automations need a premium or your own (BYOK) model. Update your model settings to enable."; + /** * Page header: title + count + "Create via chat" CTA. Creation is intent-driven * (the create_automation tool runs inside chat with a HITL approval card), so @@ -27,6 +39,8 @@ export function AutomationsHeader({ loading, canCreate, showCreateCta = true, + createDisabled = false, + disabledReason, }: AutomationsHeaderProps) { return (

@@ -40,20 +54,70 @@ export function AutomationsHeader({
{canCreate && showCreateCta && (
- - + {createDisabled ? ( + <> + } + label="Create manually" + reason={disabledReason ?? DEFAULT_DISABLED_REASON} + /> + } + label="Create via chat" + reason={disabledReason ?? DEFAULT_DISABLED_REASON} + /> + + ) : ( + <> + + + + )}
)}
); } + +function DisabledCta({ + icon, + label, + reason, + variant, +}: { + icon: ReactNode; + label: string; + reason: string; + variant?: "outline"; +}) { + return ( + + + {/* aria-disabled (not `disabled`) keeps the button focusable so the + tooltip is reachable by hover and keyboard; onClick is a no-op. */} + + + {reason} + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx index 1fd37cd3d..f3323da61 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx @@ -15,6 +15,7 @@ import { import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { type Automation, automationCreateRequest, @@ -45,6 +46,12 @@ interface AutomationBuilderFormProps { searchSpaceId: number; /** Required in edit mode; seeds the form and trigger reconciliation. */ automation?: Automation; + /** + * When set (create mode only), the search space's models aren't billable + * for automations. Submit is disabled with this reason as the tooltip; the + * orchestrator also renders the full gate alert above the form. + */ + submitDisabledReason?: string; } type Mode = "form" | "json"; @@ -66,6 +73,7 @@ export function AutomationBuilderForm({ mode, searchSpaceId, automation, + submitDisabledReason, }: AutomationBuilderFormProps) { const router = useRouter(); const { mutateAsync: createAutomation } = useAtomValue(createAutomationMutationAtom); @@ -273,6 +281,8 @@ export function AutomationBuilderForm({ } const submitLabel = mode === "edit" ? "Save changes" : "Create automation"; + // Only gate creation; editing an existing automation isn't blocked here. + const submitBlocked = mode === "create" && !!submitDisabledReason; return (
@@ -390,15 +400,39 @@ export function AutomationBuilderForm({ - + {submitBlocked ? ( + + + {/* aria-disabled keeps the button focusable so the tooltip is + reachable by hover and keyboard; onClick is a no-op. */} + + + {submitDisabledReason} + + ) : ( + + )}
); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx index 0c983aaf8..a40b0a31b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -1,5 +1,7 @@ "use client"; import { ShieldAlert } from "lucide-react"; +import { useAutomationModelEligibility } from "@/hooks/use-automation-model-eligibility"; +import { AutomationModelGateAlert } from "../components/automation-model-gate-alert"; import { AutomationBuilderForm } from "../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../hooks/use-automation-permissions"; import { AutomationNewHeader } from "./components/automation-new-header"; @@ -16,6 +18,9 @@ interface AutomationNewContentProps { */ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { const perms = useAutomationPermissions(); + const { data: eligibility, isLoading: eligibilityLoading } = useAutomationModelEligibility( + perms.canCreate ? searchSpaceId : undefined + ); if (perms.loading) { return
; @@ -33,10 +38,22 @@ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProp ); } + const modelViolations = eligibility?.violations ?? []; + const submitDisabledReason = eligibilityLoading + ? "Checking model eligibility…" + : modelViolations[0]?.reason; + return ( <> - + {modelViolations.length > 0 && ( + + )} + ); } diff --git a/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts b/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts index f4577a7a9..476d89d4c 100644 --- a/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts +++ b/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts @@ -118,6 +118,12 @@ export const updateLLMPreferencesMutationAtom = atomWithMutation((get) => { cacheKeys.newLLMConfigs.preferences(Number(searchSpaceId)), (old: Record | undefined) => ({ ...old, ...request.data }) ); + // Automation eligibility is derived from these model preferences + // (agent/image/vision). Invalidate it so the automations gate alert + // reflects the new selection without a manual refresh. + queryClient.invalidateQueries({ + queryKey: cacheKeys.automations.modelEligibility(Number(searchSpaceId)), + }); }, onError: (error: Error) => { toast.error(error.message || "Failed to update LLM preferences"); diff --git a/surfsense_web/contracts/types/automation.types.ts b/surfsense_web/contracts/types/automation.types.ts index a93249735..a1f2bd382 100644 --- a/surfsense_web/contracts/types/automation.types.ts +++ b/surfsense_web/contracts/types/automation.types.ts @@ -60,6 +60,15 @@ export const inputs = z.object({ }); export type Inputs = z.infer; +// Captured model snapshot (server-managed). Set at create time and preserved +// across edits so runs are insulated from later chat/search-space model changes. +export const automationModels = z.object({ + agent_llm_id: z.number().int().default(0), + image_generation_config_id: z.number().int().default(0), + vision_llm_config_id: z.number().int().default(0), +}); +export type AutomationModels = z.infer; + export const automationDefinition = z.object({ schema_version: z.string().default("1.0"), name: z.string().min(1).max(200), @@ -69,6 +78,7 @@ export const automationDefinition = z.object({ plan: z.array(planStep).min(1), execution: execution.default(execution.parse({})), metadata: metadata.default(metadata.parse({})), + models: automationModels.nullable().optional(), }); export type AutomationDefinition = z.infer; @@ -191,3 +201,23 @@ export const runListParams = z.object({ offset: z.number().int().min(0).default(0), }); export type RunListParams = z.infer; + +// ============================================================================= +// Model eligibility — mirror app/automations/api/automation.py (ModelEligibility) +// ============================================================================= + +export const modelEligibilityKind = z.enum(["llm", "image", "vision"]); +export type ModelEligibilityKind = z.infer; + +export const modelEligibilityViolation = z.object({ + kind: modelEligibilityKind, + config_id: z.number().nullable(), + reason: z.string(), +}); +export type ModelEligibilityViolation = z.infer; + +export const modelEligibility = z.object({ + allowed: z.boolean(), + violations: z.array(modelEligibilityViolation), +}); +export type ModelEligibility = z.infer; diff --git a/surfsense_web/hooks/use-automation-model-eligibility.ts b/surfsense_web/hooks/use-automation-model-eligibility.ts new file mode 100644 index 000000000..c9bb8b1ea --- /dev/null +++ b/surfsense_web/hooks/use-automation-model-eligibility.ts @@ -0,0 +1,25 @@ +"use client"; +import { useQuery } from "@tanstack/react-query"; +import type { ModelEligibility } from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +/** + * Whether the search space's configured models are billable for automations. + * + * Automations may only run on premium global models or user-provided (BYOK) + * models; free global models and Auto mode are blocked so every run is metered + * in premium credits. Creation surfaces use this to gate their CTAs before the + * user invests effort drafting an automation that can't be saved. + * + * Keyed by search space id (not the jotai "current scope" atom) so it can be + * used on the create route as well as the list page. + */ +export function useAutomationModelEligibility(searchSpaceId: number | undefined) { + return useQuery({ + queryKey: cacheKeys.automations.modelEligibility(searchSpaceId ?? 0), + queryFn: () => automationsApiService.getModelEligibility(searchSpaceId as number), + enabled: !!searchSpaceId, + staleTime: 60_000, + }); +} diff --git a/surfsense_web/lib/apis/automations-api.service.ts b/surfsense_web/lib/apis/automations-api.service.ts index ebe72bea5..baaf08799 100644 --- a/surfsense_web/lib/apis/automations-api.service.ts +++ b/surfsense_web/lib/apis/automations-api.service.ts @@ -6,6 +6,7 @@ import { automationCreateRequest, automationListResponse, automationUpdateRequest, + modelEligibility, type RunListParams, run, runListResponse, @@ -62,6 +63,13 @@ class AutomationsApiService { return baseApiService.delete(`${BASE}/${automationId}`); }; + // Whether the search space's models are billable for automations (premium + // global or BYOK). Used to gate creation surfaces before submit. + getModelEligibility = async (searchSpaceId: number) => { + const qs = new URLSearchParams({ search_space_id: String(searchSpaceId) }); + return baseApiService.get(`${BASE}/model-eligibility?${qs.toString()}`, modelEligibility); + }; + // ---- Triggers (sub-resource) -------------------------------------------- addTrigger = async (automationId: number, request: TriggerCreateRequest) => { diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 35724cf94..49bcd8d0e 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -134,5 +134,7 @@ export const cacheKeys = { ["automations", "runs", automationId, limit, offset] as const, run: (automationId: number, runId: number) => ["automations", "runs", automationId, runId] as const, + modelEligibility: (searchSpaceId: number) => + ["automations", "model-eligibility", searchSpaceId] as const, }, }; From 43c66008e4284ce1d80614854c15b1c1b5d9f6f8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Fri, 29 May 2026 03:30:22 -0700 Subject: [PATCH 096/133] fix(llm-role-manager): synchronize local state with preferences updates - Added useEffect to sync local assignments state with preferences when they change, ensuring the UI reflects the latest data. - Updated state initialization to use null instead of empty strings for clarity in role assignments. - Adjusted role assignment handling to correctly manage "unassigned" values and preserve Auto mode configuration during updates. --- .../components/settings/llm-role-manager.tsx | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx index 17370b8f2..9af9eec27 100644 --- a/surfsense_web/components/settings/llm-role-manager.tsx +++ b/surfsense_web/components/settings/llm-role-manager.tsx @@ -11,7 +11,7 @@ import { RefreshCw, ScanEye, } from "lucide-react"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { globalImageGenConfigsAtom, @@ -135,18 +135,39 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); - const [assignments, setAssignments] = useState(() => ({ - agent_llm_id: preferences.agent_llm_id ?? "", - document_summary_llm_id: preferences.document_summary_llm_id ?? "", - image_generation_config_id: preferences.image_generation_config_id ?? "", - vision_llm_config_id: preferences.vision_llm_config_id ?? "", + const [assignments, setAssignments] = useState>(() => ({ + agent_llm_id: preferences.agent_llm_id ?? null, + document_summary_llm_id: preferences.document_summary_llm_id ?? null, + image_generation_config_id: preferences.image_generation_config_id ?? null, + vision_llm_config_id: preferences.vision_llm_config_id ?? null, })); + // Sync local state when preferences load/change. Without this, the selects + // stay on their initial (often empty) value while the query is in flight, + // so a saved assignment — including Auto mode (id 0) — never appears. + useEffect(() => { + setAssignments({ + agent_llm_id: preferences.agent_llm_id ?? null, + document_summary_llm_id: preferences.document_summary_llm_id ?? null, + image_generation_config_id: preferences.image_generation_config_id ?? null, + vision_llm_config_id: preferences.vision_llm_config_id ?? null, + }); + }, [ + preferences.agent_llm_id, + preferences.document_summary_llm_id, + preferences.image_generation_config_id, + preferences.vision_llm_config_id, + ]); + const [savingRole, setSavingRole] = useState(null); const handleRoleAssignment = useCallback( async (prefKey: string, configId: string) => { - const value = configId === "unassigned" ? "" : parseInt(configId); + // "unassigned" clears the role (null). Every other option — including + // Auto mode, whose config id is 0 — must be sent as-is. Using a falsy + // check here (e.g. `value || undefined`) would drop id 0 and silently + // fail to persist Auto mode. + const value = configId === "unassigned" ? null : Number(configId); setAssignments((prev) => ({ ...prev, [prefKey]: value })); setSavingRole(prefKey); @@ -154,7 +175,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { try { await updatePreferences({ search_space_id: searchSpaceId, - data: { [prefKey]: value || undefined }, + data: { [prefKey]: value }, }); toast.success("Role assignment updated"); } finally { @@ -325,7 +346,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { Configuration onChange(Number(v))}> + + {selected ? ( + + {getProviderIcon(selected.provider)} + {selected.name} + + ) : ( + + )} + + + {premium.length > 0 ? ( + + Premium + {premium.map((option) => ( + + ))} + + ) : null} + {premium.length > 0 && byok.length > 0 ? : null} + {byok.length > 0 ? ( + + Your models + {byok.map((option) => ( + + ))} + + ) : null} + + + + ); +}); + +function ModelOption({ + option, + badge, +}: { + option: EligibleModelOption; + badge: "Premium" | "BYOK"; +}) { + return ( + + + {getProviderIcon(option.provider)} + {option.name} + {badge} + + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx index a40b0a31b..7dc117aab 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -1,7 +1,5 @@ "use client"; import { ShieldAlert } from "lucide-react"; -import { useAutomationModelEligibility } from "@/hooks/use-automation-model-eligibility"; -import { AutomationModelGateAlert } from "../components/automation-model-gate-alert"; import { AutomationBuilderForm } from "../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../hooks/use-automation-permissions"; import { AutomationNewHeader } from "./components/automation-new-header"; @@ -15,12 +13,13 @@ interface AutomationNewContentProps { * who can't create don't even see the form; same panel as the detail page's * access-denied state for consistency. The builder defaults to the friendly * form with a raw-JSON escape hatch. + * + * Model eligibility is no longer gated here — the builder's own model pickers + * list eligible (premium/BYOK) models, surface a per-slot notice when none + * exist, and block submit until each slot resolves. */ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { const perms = useAutomationPermissions(); - const { data: eligibility, isLoading: eligibilityLoading } = useAutomationModelEligibility( - perms.canCreate ? searchSpaceId : undefined - ); if (perms.loading) { return
; @@ -38,22 +37,10 @@ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProp ); } - const modelViolations = eligibility?.violations ?? []; - const submitDisabledReason = eligibilityLoading - ? "Checking model eligibility…" - : modelViolations[0]?.reason; - return ( <> - {modelViolations.length > 0 && ( - - )} - + ); } diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index b152f9055..1bd20bbde 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -5,6 +5,10 @@ import { useAtomValue } from "jotai"; import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react"; import Link from "next/link"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { + AutomationModelFields, + type AutomationModelSelection, +} from "@/app/dashboard/[search_space_id]/automations/components/builder/automation-model-fields"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { JsonView } from "@/components/json-view"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; @@ -12,6 +16,7 @@ import { Button } from "@/components/ui/button"; import { automationCreateRequest } from "@/contracts/types/automation.types"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; +import { useAutomationEligibleModels } from "@/hooks/use-automation-eligible-models"; import { AutomationDraftPreview } from "./automation-draft-preview"; const editArgsSchema = automationCreateRequest.omit({ search_space_id: true }); @@ -94,17 +99,71 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { const effectiveArgs = pendingEdits ?? args; const draft = useMemo(() => extractDraft(effectiveArgs), [effectiveArgs]); + // Per-automation model selection. The card always supplies models (chosen + // here, not snapshotted from the search space), so Approve dispatches an + // `edit` decision carrying `definition.models`. + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const eligibleModels = useAutomationEligibleModels(); + const [modelSelection, setModelSelection] = useState({ + agentLlmId: 0, + imageConfigId: 0, + visionConfigId: 0, + }); + // Resolve each slot during render: an explicit pick wins, else the eligible + // default. No effect seeds async hook data into state. + const resolvedModels = useMemo( + () => ({ + agentLlmId: modelSelection.agentLlmId || eligibleModels.llm.defaultId || 0, + imageConfigId: modelSelection.imageConfigId || eligibleModels.image.defaultId || 0, + visionConfigId: modelSelection.visionConfigId || eligibleModels.vision.defaultId || 0, + }), + [ + modelSelection, + eligibleModels.llm.defaultId, + eligibleModels.image.defaultId, + eligibleModels.vision.defaultId, + ] + ); + const modelsResolved = + resolvedModels.agentLlmId !== 0 && + resolvedModels.imageConfigId !== 0 && + resolvedModels.visionConfigId !== 0; + const handleApprove = useCallback(() => { - if (phase !== "pending" || !canApprove || isEditing) return; + if (phase !== "pending" || !canApprove || isEditing || !modelsResolved) return; setProcessing(); + const baseArgs = pendingEdits ?? args; + const baseDefinition = (baseArgs.definition ?? {}) as Record; + const mergedArgs = { + ...baseArgs, + definition: { + ...baseDefinition, + models: { + agent_llm_id: resolvedModels.agentLlmId, + image_generation_config_id: resolvedModels.imageConfigId, + vision_llm_config_id: resolvedModels.visionConfigId, + }, + }, + }; onDecision({ - type: pendingEdits ? "edit" : "approve", + type: "edit", edited_action: { name: interruptData.action_requests[0]?.name ?? "create_automation", - args: pendingEdits ?? args, + args: mergedArgs, }, }); - }, [phase, canApprove, isEditing, setProcessing, onDecision, interruptData, args, pendingEdits]); + }, [ + phase, + canApprove, + isEditing, + modelsResolved, + setProcessing, + onDecision, + interruptData, + args, + pendingEdits, + resolvedModels, + ]); const handleReject = useCallback(() => { if (phase !== "pending" || !canReject || isEditing) return; @@ -193,10 +252,24 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { {phase === "pending" && !isEditing && ( <> +
+
+

Models

+ setModelSelection((prev) => ({ ...prev, ...patch }))} + /> +
{canApprove && ( - diff --git a/surfsense_web/hooks/use-automation-eligible-models.ts b/surfsense_web/hooks/use-automation-eligible-models.ts new file mode 100644 index 000000000..e74994221 --- /dev/null +++ b/surfsense_web/hooks/use-automation-eligible-models.ts @@ -0,0 +1,150 @@ +"use client"; + +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { + globalImageGenConfigsAtom, + imageGenConfigsAtom, +} from "@/atoms/image-gen-config/image-gen-config-query.atoms"; +import { + globalNewLLMConfigsAtom, + llmPreferencesAtom, + newLLMConfigsAtom, +} from "@/atoms/new-llm-config/new-llm-config-query.atoms"; +import { + globalVisionLLMConfigsAtom, + visionLLMConfigsAtom, +} from "@/atoms/vision-llm-config/vision-llm-config-query.atoms"; + +/** + * A single model the user may pick for an automation slot. + */ +export interface EligibleModelOption { + id: number; + name: string; + /** Underlying model identifier (e.g. `gpt-4o`); shown as secondary text. */ + modelName: string; + provider: string; + /** `true` for user BYOK configs (positive ids), `false` for premium globals. */ + isBYOK: boolean; +} + +export interface EligibleModelKind { + options: EligibleModelOption[]; + /** Default selection: the search-space pref when eligible, else first option. */ + defaultId: number | null; + /** O(1) id → option lookup for trigger labels (avoids per-render `.find()`). */ + byId: Map; +} + +export interface AutomationEligibleModels { + llm: EligibleModelKind; + image: EligibleModelKind; + vision: EligibleModelKind; + isLoading: boolean; +} + +interface GlobalConfigLike { + id: number; + name: string; + model_name: string; + provider: string; + is_premium?: boolean; + is_auto_mode?: boolean; +} + +interface UserConfigLike { + id: number; + name: string; + model_name: string; + provider: string; +} + +/** + * Build the eligible option list for one model kind: premium globals + * (`is_premium === true`, never Auto mode) followed by all BYOK configs. + */ +function buildKind( + globals: GlobalConfigLike[] | undefined, + byok: UserConfigLike[] | undefined, + prefId: number | null | undefined +): EligibleModelKind { + const premiumGlobals: EligibleModelOption[] = (globals ?? []) + .filter((c) => c.is_premium === true && !c.is_auto_mode) + .map((c) => ({ + id: c.id, + name: c.name, + modelName: c.model_name, + provider: c.provider, + isBYOK: false, + })); + + const byokOptions: EligibleModelOption[] = (byok ?? []).map((c) => ({ + id: c.id, + name: c.name, + modelName: c.model_name, + provider: c.provider, + isBYOK: true, + })); + + const options = [...premiumGlobals, ...byokOptions]; + const byId = new Map(options.map((o) => [o.id, o])); + + let defaultId: number | null = null; + if (prefId != null && byId.has(prefId)) { + defaultId = prefId; + } else if (options.length > 0) { + defaultId = options[0].id; + } + + return { options, defaultId, byId }; +} + +/** + * Lists the LLM / image / vision models that are eligible for automations + * (premium globals + user BYOK — never free globals or Auto mode), with a + * default selection seeded from the search space's role preferences. + * + * Everything is derived during render from the existing config query atoms; + * there are no effects, so option lists/maps keep stable references. + */ +export function useAutomationEligibleModels(): AutomationEligibleModels { + const { data: llmUserConfigs, isLoading: llmUserLoading } = useAtomValue(newLLMConfigsAtom); + const { data: llmGlobalConfigs, isLoading: llmGlobalLoading } = + useAtomValue(globalNewLLMConfigsAtom); + const { data: preferences, isLoading: prefsLoading } = useAtomValue(llmPreferencesAtom); + const { data: imageGlobalConfigs, isLoading: imageGlobalLoading } = + useAtomValue(globalImageGenConfigsAtom); + const { data: imageUserConfigs, isLoading: imageUserLoading } = useAtomValue(imageGenConfigsAtom); + const { data: visionGlobalConfigs, isLoading: visionGlobalLoading } = useAtomValue( + globalVisionLLMConfigsAtom + ); + const { data: visionUserConfigs, isLoading: visionUserLoading } = + useAtomValue(visionLLMConfigsAtom); + + const llm = useMemo( + () => buildKind(llmGlobalConfigs, llmUserConfigs, preferences?.agent_llm_id), + [llmGlobalConfigs, llmUserConfigs, preferences?.agent_llm_id] + ); + + const image = useMemo( + () => buildKind(imageGlobalConfigs, imageUserConfigs, preferences?.image_generation_config_id), + [imageGlobalConfigs, imageUserConfigs, preferences?.image_generation_config_id] + ); + + const vision = useMemo( + () => buildKind(visionGlobalConfigs, visionUserConfigs, preferences?.vision_llm_config_id), + [visionGlobalConfigs, visionUserConfigs, preferences?.vision_llm_config_id] + ); + + const isLoading = + llmUserLoading || + llmGlobalLoading || + prefsLoading || + imageGlobalLoading || + imageUserLoading || + visionGlobalLoading || + visionUserLoading; + + return useMemo(() => ({ llm, image, vision, isLoading }), [llm, image, vision, isLoading]); +} diff --git a/surfsense_web/lib/automations/builder-schema.ts b/surfsense_web/lib/automations/builder-schema.ts index a9349c56f..c2bd69209 100644 --- a/surfsense_web/lib/automations/builder-schema.ts +++ b/surfsense_web/lib/automations/builder-schema.ts @@ -66,6 +66,19 @@ export const builderExecutionSchema = z.object({ }); export type BuilderExecution = z.infer; +/** + * Per-automation model selection. ``0`` means "unset" — the builder resolves it + * to the eligible default during render, and the resolved (non-zero) ids are + * written onto ``definition.models`` at submit so the run is insulated from + * later chat/search-space model changes. + */ +export const builderModelsSchema = z.object({ + agentLlmId: z.number().int(), + imageConfigId: z.number().int(), + visionConfigId: z.number().int(), +}); +export type BuilderModels = z.infer; + export const builderFormSchema = z.object({ name: z.string().trim().min(1, "Give your automation a name").max(200), description: z.string().trim().max(2000).nullable(), @@ -77,6 +90,8 @@ export const builderFormSchema = z.object({ tags: z.array(z.string()), /** Carried through from an edited definition so we don't drop it. */ goal: z.string().nullable(), + /** Selected agent/image/vision models (``0`` = use the eligible default). */ + models: builderModelsSchema, }); export type BuilderForm = z.infer; @@ -132,6 +147,7 @@ export function createEmptyForm(): BuilderForm { }, tags: [], goal: null, + models: { agentLlmId: 0, imageConfigId: 0, visionConfigId: 0 }, }; } @@ -218,9 +234,26 @@ function buildDefinition(form: BuilderForm): AutomationDefinition { on_failure: [], }, metadata: { tags: form.tags }, + // Only emit models when fully resolved (the builder seeds non-zero + // defaults before submit). A zero/unset triple is omitted so the + // backend falls back to the search-space snapshot. + ...(hasResolvedModels(form.models) + ? { + models: { + agent_llm_id: form.models.agentLlmId, + image_generation_config_id: form.models.imageConfigId, + vision_llm_config_id: form.models.visionConfigId, + }, + } + : {}), } as unknown as AutomationDefinition; } +/** True once every model slot holds a concrete (non-zero) id. */ +export function hasResolvedModels(models: BuilderModels): boolean { + return models.agentLlmId !== 0 && models.imageConfigId !== 0 && models.visionConfigId !== 0; +} + /** The desired schedule trigger for this form, or ``null`` if none. */ export function buildScheduleTrigger(form: BuilderForm): TriggerCreateRequest | null { if (!form.schedule) return null; @@ -434,6 +467,8 @@ export function hydrateForm( ? metadata.tags.filter((tag): tag is string => typeof tag === "string") : []; + const models = modelsFromDefinition(d.models); + return { formable: true, form: { @@ -455,10 +490,22 @@ export function hydrateForm( }, tags, goal: typeof d.goal === "string" ? d.goal : null, + models, }, }; } +/** Read a captured ``definition.models`` snapshot into the form's model slots. */ +function modelsFromDefinition(raw: unknown): BuilderModels { + const m = asRecord(raw); + const num = (value: unknown) => (typeof value === "number" ? value : 0); + return { + agentLlmId: num(m.agent_llm_id), + imageConfigId: num(m.image_generation_config_id), + visionConfigId: num(m.vision_llm_config_id), + }; +} + /** * Project an existing automation into the builder form for editing. */ From 92b1d7a9f77d0480d1df6c5ad292f3c79aaf773b Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sat, 30 May 2026 01:13:21 -0700 Subject: [PATCH 121/133] feat(automations): enhance tracking for automation lifecycle events - Added tracking for automation creation, updates, deletions, and trigger modifications, including success and failure events. - Implemented event tracking in the automation creation process, including chat approval and rejection scenarios. - Updated the instrumentation client to ensure correct typing for PostHog integration. - Refactored existing mutation atoms to include tracking calls for automation-related actions, improving analytics capabilities. --- .../automations/automations-mutation.atoms.ts | 112 +++++++++++++- .../tool-ui/automation/create-automation.tsx | 66 +++++++- surfsense_web/instrumentation-client.ts | 7 +- surfsense_web/lib/posthog/events.ts | 145 +++++++++++++++++- 4 files changed, 316 insertions(+), 14 deletions(-) diff --git a/surfsense_web/atoms/automations/automations-mutation.atoms.ts b/surfsense_web/atoms/automations/automations-mutation.atoms.ts index f5e4fd5f4..a81cd1578 100644 --- a/surfsense_web/atoms/automations/automations-mutation.atoms.ts +++ b/surfsense_web/atoms/automations/automations-mutation.atoms.ts @@ -7,6 +7,21 @@ import type { TriggerUpdateRequest, } from "@/contracts/types/automation.types"; import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { + trackAutomationCreated, + trackAutomationCreateFailed, + trackAutomationDeleted, + trackAutomationDeleteFailed, + trackAutomationStatusChanged, + trackAutomationTriggerAdded, + trackAutomationTriggerAddFailed, + trackAutomationTriggerRemoved, + trackAutomationTriggerRemoveFailed, + trackAutomationTriggerUpdated, + trackAutomationTriggerUpdateFailed, + trackAutomationUpdated, + trackAutomationUpdateFailed, +} from "@/lib/posthog/events"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { queryClient } from "@/lib/query-client/client"; @@ -33,13 +48,28 @@ export const createAutomationMutationAtom = atomWithMutation(() => ({ mutationFn: async (request: AutomationCreateRequest) => { return automationsApiService.createAutomation(request); }, - onSuccess: (_, variables) => { + onSuccess: (automation, variables) => { invalidateList(variables.search_space_id); toast.success("Automation created"); + trackAutomationCreated({ + search_space_id: variables.search_space_id, + automation_id: automation.id, + task_count: variables.definition.plan.length, + trigger_type: variables.triggers?.[0]?.type ?? "none", + has_schedule: (variables.triggers?.length ?? 0) > 0, + agent_llm_id: variables.definition.models?.agent_llm_id, + image_generation_config_id: variables.definition.models?.image_generation_config_id, + vision_llm_config_id: variables.definition.models?.vision_llm_config_id, + tags_count: variables.definition.metadata?.tags?.length, + }); }, - onError: (error: Error) => { + onError: (error: Error, variables) => { console.error("Error creating automation:", error); toast.error("Failed to create automation"); + trackAutomationCreateFailed({ + search_space_id: variables.search_space_id, + error: error.message, + }); }, })); @@ -52,10 +82,32 @@ export const updateAutomationMutationAtom = atomWithMutation(() => ({ invalidateDetail(vars.automationId); invalidateList(automation.search_space_id); toast.success("Automation updated"); + // A status-only patch (pause/resume/archive) is a distinct action from a + // definition/name edit, so split it into its own event. + if (vars.patch.status && !vars.patch.definition) { + trackAutomationStatusChanged({ + automation_id: vars.automationId, + search_space_id: automation.search_space_id, + next_status: vars.patch.status, + }); + } else { + trackAutomationUpdated({ + automation_id: vars.automationId, + search_space_id: automation.search_space_id, + has_definition_change: !!vars.patch.definition, + has_name_change: vars.patch.name != null, + has_description_change: vars.patch.description !== undefined, + task_count: vars.patch.definition?.plan?.length, + }); + } }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error updating automation:", error); toast.error("Failed to update automation"); + trackAutomationUpdateFailed({ + automation_id: vars.automationId, + error: error.message, + }); }, })); @@ -69,10 +121,18 @@ export const deleteAutomationMutationAtom = atomWithMutation(() => ({ invalidateList(vars.searchSpaceId); invalidateDetail(vars.automationId); toast.success("Automation deleted"); + trackAutomationDeleted({ + automation_id: vars.automationId, + search_space_id: vars.searchSpaceId, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error deleting automation:", error); toast.error("Failed to delete automation"); + trackAutomationDeleteFailed({ + automation_id: vars.automationId, + error: error.message, + }); }, })); @@ -81,13 +141,24 @@ export const addTriggerMutationAtom = atomWithMutation(() => ({ mutationFn: async (vars: { automationId: number; payload: TriggerCreateRequest }) => { return automationsApiService.addTrigger(vars.automationId, vars.payload); }, - onSuccess: (_, vars) => { + onSuccess: (trigger, vars) => { invalidateDetail(vars.automationId); toast.success("Trigger added"); + trackAutomationTriggerAdded({ + automation_id: vars.automationId, + trigger_id: trigger.id, + trigger_type: trigger.type, + enabled: trigger.enabled, + has_cron: !!trigger.params?.cron, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error adding trigger:", error); toast.error("Failed to add trigger"); + trackAutomationTriggerAddFailed({ + automation_id: vars.automationId, + error: error.message, + }); }, })); @@ -103,10 +174,26 @@ export const updateTriggerMutationAtom = atomWithMutation(() => ({ onSuccess: (_, vars) => { invalidateDetail(vars.automationId); toast.success("Trigger updated"); + const change: "enabled" | "params" | "other" = vars.patch.params + ? "params" + : vars.patch.enabled !== undefined && vars.patch.enabled !== null + ? "enabled" + : "other"; + trackAutomationTriggerUpdated({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + change, + enabled: vars.patch.enabled ?? undefined, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error updating trigger:", error); toast.error("Failed to update trigger"); + trackAutomationTriggerUpdateFailed({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + error: error.message, + }); }, })); @@ -119,9 +206,18 @@ export const removeTriggerMutationAtom = atomWithMutation(() => ({ onSuccess: (vars) => { invalidateDetail(vars.automationId); toast.success("Trigger removed"); + trackAutomationTriggerRemoved({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error removing trigger:", error); toast.error("Failed to remove trigger"); + trackAutomationTriggerRemoveFailed({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + error: error.message, + }); }, })); diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index 1bd20bbde..d4cc0ec4d 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -4,7 +4,7 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react"; import Link from "next/link"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { AutomationModelFields, type AutomationModelSelection, @@ -17,6 +17,13 @@ import { automationCreateRequest } from "@/contracts/types/automation.types"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; import { useAutomationEligibleModels } from "@/hooks/use-automation-eligible-models"; +import { + trackAutomationChatApproved, + trackAutomationChatCreateFailed, + trackAutomationChatCreateSucceeded, + trackAutomationChatDraftEdited, + trackAutomationChatRejected, +} from "@/lib/posthog/events"; import { AutomationDraftPreview } from "./automation-draft-preview"; const editArgsSchema = automationCreateRequest.omit({ search_space_id: true }); @@ -145,6 +152,19 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { }, }, }; + const plan = Array.isArray(baseDefinition.plan) ? baseDefinition.plan : []; + const triggers = Array.isArray(baseArgs.triggers) ? baseArgs.triggers : []; + trackAutomationChatApproved({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + edited: pendingEdits !== null, + task_count: plan.length, + trigger_type: + (triggers[0] as { type?: string } | undefined)?.type ?? + (triggers.length ? undefined : "none"), + agent_llm_id: resolvedModels.agentLlmId, + image_generation_config_id: resolvedModels.imageConfigId, + vision_llm_config_id: resolvedModels.visionConfigId, + }); onDecision({ type: "edit", edited_action: { @@ -163,13 +183,17 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { args, pendingEdits, resolvedModels, + searchSpaceId, ]); const handleReject = useCallback(() => { if (phase !== "pending" || !canReject || isEditing) return; setRejected(); + trackAutomationChatRejected({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); onDecision({ type: "reject", message: "User rejected the automation draft." }); - }, [phase, canReject, isEditing, setRejected, onDecision]); + }, [phase, canReject, isEditing, setRejected, onDecision, searchSpaceId]); useEffect(() => { if (isEditing) return; @@ -242,6 +266,9 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { onSave={(parsed) => { setPendingEdits(parsed); setIsEditing(false); + trackAutomationChatDraftEdited({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); }} onCancel={() => setIsEditing(false)} /> @@ -356,6 +383,17 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { function SavedCard({ result }: { result: SavedResult }) { const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const tracked = useRef(false); + useEffect(() => { + if (tracked.current) return; + tracked.current = true; + trackAutomationChatCreateSucceeded({ + automation_id: result.automation_id, + name: result.name, + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, [result.automation_id, result.name, searchSpaceId]); + const detailHref = searchSpaceId ? `/dashboard/${searchSpaceId}/automations/${result.automation_id}` : null; @@ -388,6 +426,18 @@ function SavedCard({ result }: { result: SavedResult }) { } function InvalidCard({ result }: { result: InvalidResult }) { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const tracked = useRef(false); + useEffect(() => { + if (tracked.current) return; + tracked.current = true; + trackAutomationChatCreateFailed({ + reason: "invalid", + issue_count: result.issues.length, + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, [result.issues.length, searchSpaceId]); + return (
@@ -411,6 +461,18 @@ function InvalidCard({ result }: { result: InvalidResult }) { } function ErrorCard({ result }: { result: ErrorResult }) { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const tracked = useRef(false); + useEffect(() => { + if (tracked.current) return; + tracked.current = true; + trackAutomationChatCreateFailed({ + reason: "error", + message: result.message, + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, [result.message, searchSpaceId]); + return (
diff --git a/surfsense_web/instrumentation-client.ts b/surfsense_web/instrumentation-client.ts index 3ae97fc0b..3eab87dec 100644 --- a/surfsense_web/instrumentation-client.ts +++ b/surfsense_web/instrumentation-client.ts @@ -88,9 +88,12 @@ async function initPostHog() { } return event; }, - loaded: (ph) => { + loaded: () => { if (typeof window !== "undefined") { - window.posthog = ph; + // `loaded` hands back a `PostHogInterface`, but it's the same + // singleton as the default import (typed `PostHog`); use that to + // keep `window.posthog` correctly typed. + window.posthog = posthog; } }, }); diff --git a/surfsense_web/lib/posthog/events.ts b/surfsense_web/lib/posthog/events.ts index a584f9b6f..4dc644d5e 100644 --- a/surfsense_web/lib/posthog/events.ts +++ b/surfsense_web/lib/posthog/events.ts @@ -1,5 +1,5 @@ +import posthog from "posthog-js"; import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; -import type { ConnectorTelemetryMeta } from "@/lib/connector-telemetry"; import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; /** @@ -19,6 +19,7 @@ import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; * - connector: External connector events (all lifecycle stages) * - contact: Contact form events * - settings: Settings changes + * - automation: Automation lifecycle (create/update/delete/trigger/chat) * - marketing: Marketing/referral tracking */ @@ -33,7 +34,7 @@ function safeCapture(event: string, properties?: Record) { /** * Drop undefined values so PostHog doesn't log `"foo": undefined` noise. */ -function compact>(obj: T): Record { +function compact(obj: T): Record { const out: Record = {}; for (const [k, v] of Object.entries(obj)) { if (v !== undefined) out[k] = v; @@ -598,6 +599,146 @@ export function trackReferralLanding(refCode: string, landingUrl: string) { }); } +// ============================================ +// AUTOMATION EVENTS +// ============================================ + +interface AutomationCreatedProps { + search_space_id: number; + automation_id: number; + task_count?: number; + trigger_type?: string; + has_schedule?: boolean; + agent_llm_id?: number; + image_generation_config_id?: number; + vision_llm_config_id?: number; + tags_count?: number; +} + +export function trackAutomationCreated(props: AutomationCreatedProps) { + safeCapture("automation_created", compact(props)); +} + +export function trackAutomationCreateFailed(props: { search_space_id?: number; error?: string }) { + safeCapture("automation_create_failed", compact(props)); +} + +export function trackAutomationUpdated(props: { + automation_id: number; + search_space_id?: number; + has_definition_change?: boolean; + has_name_change?: boolean; + has_description_change?: boolean; + task_count?: number; +}) { + safeCapture("automation_updated", compact(props)); +} + +export function trackAutomationStatusChanged(props: { + automation_id: number; + search_space_id?: number; + next_status: string; +}) { + safeCapture("automation_status_changed", compact(props)); +} + +export function trackAutomationUpdateFailed(props: { automation_id: number; error?: string }) { + safeCapture("automation_update_failed", compact(props)); +} + +export function trackAutomationDeleted(props: { automation_id: number; search_space_id?: number }) { + safeCapture("automation_deleted", compact(props)); +} + +export function trackAutomationDeleteFailed(props: { automation_id: number; error?: string }) { + safeCapture("automation_delete_failed", compact(props)); +} + +export function trackAutomationTriggerAdded(props: { + automation_id: number; + trigger_id?: number; + trigger_type?: string; + enabled?: boolean; + has_cron?: boolean; +}) { + safeCapture("automation_trigger_added", compact(props)); +} + +export function trackAutomationTriggerAddFailed(props: { automation_id: number; error?: string }) { + safeCapture("automation_trigger_add_failed", compact(props)); +} + +export function trackAutomationTriggerUpdated(props: { + automation_id: number; + trigger_id: number; + change?: "enabled" | "params" | "other"; + enabled?: boolean; +}) { + safeCapture("automation_trigger_updated", compact(props)); +} + +export function trackAutomationTriggerUpdateFailed(props: { + automation_id: number; + trigger_id: number; + error?: string; +}) { + safeCapture("automation_trigger_update_failed", compact(props)); +} + +export function trackAutomationTriggerRemoved(props: { + automation_id: number; + trigger_id: number; +}) { + safeCapture("automation_trigger_removed", compact(props)); +} + +export function trackAutomationTriggerRemoveFailed(props: { + automation_id: number; + trigger_id: number; + error?: string; +}) { + safeCapture("automation_trigger_remove_failed", compact(props)); +} + +interface AutomationChatDecisionProps { + search_space_id?: number; + edited?: boolean; + task_count?: number; + trigger_type?: string; + agent_llm_id?: number; + image_generation_config_id?: number; + vision_llm_config_id?: number; +} + +export function trackAutomationChatApproved(props: AutomationChatDecisionProps) { + safeCapture("automation_chat_approved", compact(props)); +} + +export function trackAutomationChatRejected(props: { search_space_id?: number }) { + safeCapture("automation_chat_rejected", compact(props)); +} + +export function trackAutomationChatDraftEdited(props: { search_space_id?: number }) { + safeCapture("automation_chat_draft_edited", compact(props)); +} + +export function trackAutomationChatCreateSucceeded(props: { + automation_id: number; + name?: string; + search_space_id?: number; +}) { + safeCapture("automation_chat_create_succeeded", compact(props)); +} + +export function trackAutomationChatCreateFailed(props: { + reason: "invalid" | "error"; + search_space_id?: number; + issue_count?: number; + message?: string; +}) { + safeCapture("automation_chat_create_failed", compact(props)); +} + // ============================================ // USER IDENTIFICATION // ============================================ From 0f2e3c765556036b4faca4ff713785886ce0180f Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 15:58:21 -0700 Subject: [PATCH 122/133] refactor: anonymous/free chat experience - Enhanced lambda function formatting in `_after_commit` for better clarity. - Simplified generator expression in `_match_condition` for improved readability. - Streamlined function signature in `_eligible` for consistency. - Updated imports and refactored anonymous chat routes to use a new agent creation method. - Added a new function `_load_anon_document` to handle document loading from Redis. - Improved UI components by replacing legacy structures with modern alternatives, including alerts and separators. - Refactored quota-related components to utilize new alert structures for better user feedback. - Cleaned up unused variables and optimized component states for performance. --- .../app/agents/new_chat/anonymous_agent.py | 168 +++++++++++++++ .../triggers/builtin/event/filter.py | 3 +- .../triggers/builtin/event/selector.py | 4 +- .../app/routes/anonymous_chat_routes.py | 57 +++-- surfsense_backend/app/session_events.py | 8 +- .../tests/unit/event_bus/test_catalog.py | 4 + .../assistant-ui/inline-citation.tsx | 4 +- .../components/free-chat/free-chat-page.tsx | 41 ++-- .../components/free-chat/free-composer.tsx | 18 +- .../free-chat/free-model-selector.tsx | 199 ++++++------------ .../components/free-chat/free-right-panel.tsx | 32 ++- .../components/free-chat/quota-bar.tsx | 33 ++- .../free-chat/quota-warning-banner.tsx | 86 ++++---- .../layout/ui/sidebar/DocumentsSidebar.tsx | 5 +- .../new-chat/document-mention-picker.tsx | 8 +- surfsense_web/components/ui/empty.tsx | 94 +++++++++ surfsense_web/contexts/anonymous-mode.tsx | 7 +- 17 files changed, 493 insertions(+), 278 deletions(-) create mode 100644 surfsense_backend/app/agents/new_chat/anonymous_agent.py create mode 100644 surfsense_web/components/ui/empty.tsx diff --git a/surfsense_backend/app/agents/new_chat/anonymous_agent.py b/surfsense_backend/app/agents/new_chat/anonymous_agent.py new file mode 100644 index 000000000..c783d9a45 --- /dev/null +++ b/surfsense_backend/app/agents/new_chat/anonymous_agent.py @@ -0,0 +1,168 @@ +"""Minimal anonymous / free-chat agent. + +The no-login chat experience must stay dead simple: the user asks a question +and the model answers, optionally using ``web_search`` and an optionally +uploaded **read-only** document. We deliberately bypass the full SurfSense deep +agent stack (filesystem, file-intent, knowledge-base persistence, subagents, +skills, memory) because those middlewares stage or persist "documents" that an +anonymous session can never see again -- which produced phantom +"I saved it to a file" answers for free users. + +For any other SurfSense capability the model is instructed (via the system +prompt built here) to tell the user to create a free account instead of +pretending to perform the action. +""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import Any + +from deepagents.backends import StateBackend +from langchain.agents import create_agent +from langchain.agents.middleware import ( + ModelCallLimitMiddleware, + ToolCallLimitMiddleware, +) +from langchain_core.language_models import BaseChatModel +from langgraph.types import Checkpointer + +from app.agents.new_chat.context import SurfSenseContextSchema +from app.agents.new_chat.middleware import ( + RetryAfterMiddleware, + create_surfsense_compaction_middleware, +) +from app.agents.new_chat.tools.web_search import create_web_search_tool + +# Cap how much of an uploaded document we inline into the system prompt. The +# upload endpoint allows files up to several MB, but the doc is re-sent on +# every turn and counts against the anonymous token quota, so we bound it. +_MAX_DOC_CHARS = 50_000 + + +def build_anonymous_system_prompt(anon_doc: dict[str, Any] | None = None) -> str: + """Build the system prompt for the minimal anonymous chat agent. + + The prompt keeps the assistant focused on plain Q/A + web search, inlines + any uploaded document as read-only context, and redirects every other + SurfSense feature to account registration. + """ + today = datetime.now(UTC).strftime("%A, %B %d, %Y") + + doc_section = "" + if anon_doc: + title = str(anon_doc.get("title") or "uploaded_document") + content = str(anon_doc.get("content") or "") + truncated = content[:_MAX_DOC_CHARS] + truncation_note = "" + if len(content) > _MAX_DOC_CHARS: + truncation_note = ( + "\n\n[Note: the document was truncated because it is large; " + "only the beginning is shown.]" + ) + doc_section = ( + "\n\n## Uploaded document (read-only)\n" + f'The user uploaded a document named "{title}". Its contents are ' + "provided below for reference only. You may read it and answer " + "questions about it, but you cannot modify, save, or store it.\n\n" + f'\n' + f"{truncated}{truncation_note}\n" + "" + ) + + return ( + "You are SurfSense's free AI assistant, available to everyone without " + "login.\n\n" + f"Today's date is {today}.\n\n" + "## How to help\n" + "- Answer the user's questions directly and conversationally. You are " + "a straightforward question-and-answer assistant.\n" + "- When a question needs current, real-time, or factual information " + "from the internet (news, prices, weather, recent events, live data), " + "use the `web_search` tool. Otherwise, answer directly from your own " + "knowledge.\n" + "- Be concise, accurate, and helpful. Use Markdown formatting when it " + "improves readability." + f"{doc_section}\n\n" + "## What is not available here\n" + "This is the free, no-login experience. You CANNOT save files or " + "notes, generate reports, podcasts, resumes, presentations, or images, " + "search or build a knowledge base, connect to apps (Gmail, Google " + "Drive, Notion, Slack, Calendar, Discord, and similar), set up " + "automations, or remember anything across sessions.\n\n" + "If the user asks for any of these, do NOT pretend to do them and " + "never claim you saved, created, or stored anything. Instead, briefly " + "let them know the feature requires a free SurfSense account and " + "invite them to create one at https://www.surfsense.com. Then offer to " + "help with what you can do here (answering questions and searching the " + "web)." + ) + + +async def create_anonymous_chat_agent( + *, + llm: BaseChatModel, + checkpointer: Checkpointer, + anon_session_id: str | None = None, + anon_doc: dict[str, Any] | None = None, + enable_web_search: bool = True, +): + """Create a minimal Q/A agent for anonymous / free chat. + + Unlike :func:`create_surfsense_deep_agent`, this agent has no filesystem, + file-intent, knowledge-base persistence, subagent, skills, or memory + middleware. Its only tool is ``web_search`` (when ``enable_web_search`` is + True), and any uploaded document is injected into the system prompt as + read-only context. + + Args: + llm: The chat model to use (already built by the caller). + checkpointer: LangGraph checkpointer for the ephemeral anon thread. + anon_session_id: Anonymous session id (used only for telemetry/metadata). + anon_doc: Optional ``{"title", "content"}`` for an uploaded document. + enable_web_search: When False, the agent runs as a pure LLM with no + tools (used when the user toggles web search off). + """ + tools = ( + [create_web_search_tool(search_space_id=None, available_connectors=None)] + if enable_web_search + else [] + ) + + # Reliability-only middleware. Nothing here touches the database or + # filesystem: call limits guard against loops, compaction summarises long + # histories into in-graph state, and retry handles provider rate limits. + middleware: list[Any] = [ + ModelCallLimitMiddleware(thread_limit=120, run_limit=80, exit_behavior="end"), + ] + if tools: + middleware.append( + ToolCallLimitMiddleware( + thread_limit=300, run_limit=80, exit_behavior="continue" + ) + ) + middleware.append(create_surfsense_compaction_middleware(llm, StateBackend)) + middleware.append(RetryAfterMiddleware(max_retries=3)) + + system_prompt = build_anonymous_system_prompt(anon_doc) + + agent = create_agent( + llm, + system_prompt=system_prompt, + tools=tools, + middleware=middleware, + context_schema=SurfSenseContextSchema, + checkpointer=checkpointer, + ) + return agent.with_config( + { + "recursion_limit": 40, + "metadata": { + "ls_integration": "surfsense_anonymous_chat", + "anon_session_id": anon_session_id, + }, + } + ) + + +__all__ = ["build_anonymous_system_prompt", "create_anonymous_chat_agent"] diff --git a/surfsense_backend/app/automations/triggers/builtin/event/filter.py b/surfsense_backend/app/automations/triggers/builtin/event/filter.py index 9f13cd51e..742281fc6 100644 --- a/surfsense_backend/app/automations/triggers/builtin/event/filter.py +++ b/surfsense_backend/app/automations/triggers/builtin/event/filter.py @@ -65,8 +65,7 @@ def _match_condition(condition: Any, actual: Any) -> bool: return False if isinstance(condition, dict): return all( - _apply_operator(op, operand, actual) - for op, operand in condition.items() + _apply_operator(op, operand, actual) for op, operand in condition.items() ) return actual == condition diff --git a/surfsense_backend/app/automations/triggers/builtin/event/selector.py b/surfsense_backend/app/automations/triggers/builtin/event/selector.py index 9c000e716..ee00a6094 100644 --- a/surfsense_backend/app/automations/triggers/builtin/event/selector.py +++ b/surfsense_backend/app/automations/triggers/builtin/event/selector.py @@ -41,9 +41,7 @@ async def _select_and_start(event_dict: dict[str, Any]) -> None: await _start_one(session, trigger=trigger, event=event) -async def _eligible( - session: AsyncSession, *, event: Event -) -> list[AutomationTrigger]: +async def _eligible(session: AsyncSession, *, event: Event) -> list[AutomationTrigger]: """Enabled ``event`` triggers for this event type whose filter matches.""" stmt = select(AutomationTrigger).where( AutomationTrigger.type == TriggerType.EVENT, diff --git a/surfsense_backend/app/routes/anonymous_chat_routes.py b/surfsense_backend/app/routes/anonymous_chat_routes.py index f9d694e5a..eb952e684 100644 --- a/surfsense_backend/app/routes/anonymous_chat_routes.py +++ b/surfsense_backend/app/routes/anonymous_chat_routes.py @@ -351,10 +351,9 @@ async def stream_anonymous_chat( async def _generate(): from langchain_core.messages import AIMessage, HumanMessage - from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent + from app.agents.new_chat.anonymous_agent import create_anonymous_chat_agent from app.agents.new_chat.checkpointer import get_checkpointer from app.db import shielded_async_session - from app.services.connector_service import ConnectorService from app.services.new_streaming_service import VercelStreamingService from app.services.token_tracking_service import start_turn from app.tasks.chat.stream_new_chat import StreamResult, _stream_agent_events @@ -363,24 +362,23 @@ async def stream_anonymous_chat( streaming_service = VercelStreamingService() try: - async with shielded_async_session() as session: - connector_service = ConnectorService(session, search_space_id=None) + async with shielded_async_session(): checkpointer = await get_checkpointer() anon_thread_id = f"anon-{session_id}-{request_id}" - agent = await create_surfsense_deep_agent( + # Load the optional uploaded document as read-only context. + anon_doc = await _load_anon_document(session_id) + + # Minimal Q/A agent: web_search only (when enabled), no + # filesystem / persistence / subagents. The uploaded document + # is injected into the system prompt as read-only context. + agent = await create_anonymous_chat_agent( llm=llm, - search_space_id=0, - db_session=session, - connector_service=connector_service, checkpointer=checkpointer, - user_id=None, - thread_id=None, - agent_config=agent_config, - enabled_tools=list(enabled_for_agent), - disabled_tools=None, anon_session_id=session_id, + anon_doc=anon_doc, + enable_web_search="web_search" in enabled_for_agent, ) langchain_messages = [] @@ -396,7 +394,6 @@ async def stream_anonymous_chat( input_state = { "messages": langchain_messages, - "search_space_id": 0, } langgraph_config = { @@ -500,6 +497,38 @@ ANON_ALLOWED_EXTENSIONS = PLAINTEXT_EXTENSIONS | DIRECT_CONVERT_EXTENSIONS ANON_DOC_REDIS_PREFIX = "anon:doc:" +async def _load_anon_document(session_id: str) -> dict[str, Any] | None: + """Read the anonymous session's uploaded document from Redis. + + Returns ``{"title", "content"}`` for read-only injection into the agent's + system prompt, or ``None`` when nothing was uploaded for this session. + """ + import json as _json + + import redis.asyncio as aioredis + + redis_client = aioredis.from_url(config.REDIS_APP_URL, decode_responses=True) + redis_key = f"{ANON_DOC_REDIS_PREFIX}{session_id}" + try: + data = await redis_client.get(redis_key) + if not data: + return None + payload = _json.loads(data) + except Exception as exc: # pragma: no cover - defensive + logger.warning("Failed to load anonymous document from Redis: %s", exc) + return None + finally: + await redis_client.aclose() + + content = str(payload.get("content") or "") + if not content: + return None + return { + "title": str(payload.get("filename") or "uploaded_document"), + "content": content, + } + + class AnonDocResponse(BaseModel): filename: str size_bytes: int diff --git a/surfsense_backend/app/session_events.py b/surfsense_backend/app/session_events.py index b2ec57fcc..048df2b46 100644 --- a/surfsense_backend/app/session_events.py +++ b/surfsense_backend/app/session_events.py @@ -79,9 +79,11 @@ def _after_commit(session: Session) -> None: ] for task in tasks: task.add_done_callback( - lambda t: logger.error("event publish failed: %s", t.exception()) - if not t.cancelled() and t.exception() - else None + lambda t: ( + logger.error("event publish failed: %s", t.exception()) + if not t.cancelled() and t.exception() + else None + ) ) diff --git a/surfsense_backend/tests/unit/event_bus/test_catalog.py b/surfsense_backend/tests/unit/event_bus/test_catalog.py index cbd377b02..b09482bea 100644 --- a/surfsense_backend/tests/unit/event_bus/test_catalog.py +++ b/surfsense_backend/tests/unit/event_bus/test_catalog.py @@ -24,6 +24,7 @@ def _event_type(type_: str = "test.thing") -> EventType: def test_register_then_get_returns_the_event_type(isolated_event_catalog: None) -> None: from app.event_bus.catalog import catalog + catalog.register(_event_type()) assert catalog.get("test.thing") is not None @@ -32,12 +33,14 @@ def test_register_then_get_returns_the_event_type(isolated_event_catalog: None) def test_get_unknown_type_returns_none(isolated_event_catalog: None) -> None: from app.event_bus.catalog import catalog + assert catalog.get("does.not.exist") is None def test_register_duplicate_type_raises(isolated_event_catalog: None) -> None: """A type is a contract; registering it twice is a bug, not an override.""" from app.event_bus.catalog import catalog + catalog.register(_event_type()) with pytest.raises(ValueError, match="already registered"): @@ -47,6 +50,7 @@ def test_register_duplicate_type_raises(isolated_event_catalog: None) -> None: def test_all_is_a_defensive_snapshot(isolated_event_catalog: None) -> None: """Mutating the returned dict must not corrupt the registry.""" from app.event_bus.catalog import catalog + catalog.register(_event_type()) snapshot = catalog.all() diff --git a/surfsense_web/components/assistant-ui/inline-citation.tsx b/surfsense_web/components/assistant-ui/inline-citation.tsx index cbf3c82d6..6a8f2e035 100644 --- a/surfsense_web/components/assistant-ui/inline-citation.tsx +++ b/surfsense_web/components/assistant-ui/inline-citation.tsx @@ -51,7 +51,9 @@ export const InlineCitation: FC = ({ chunkId, isDocsChunk = doc - {isDocsChunk ? "Documentation reference" : "Uploaded document"} + + {isDocsChunk ? "Documentation reference" : "Uploaded document"} + ); } diff --git a/surfsense_web/components/free-chat/free-chat-page.tsx b/surfsense_web/components/free-chat/free-chat-page.tsx index 927eaef87..2ee026cc3 100644 --- a/surfsense_web/components/free-chat/free-chat-page.tsx +++ b/surfsense_web/components/free-chat/free-chat-page.tsx @@ -15,6 +15,7 @@ import { type TokenUsageData, TokenUsageProvider, } from "@/components/assistant-ui/token-usage-context"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; import { TimelineDataUI } from "@/features/chat-messages/timeline"; import { @@ -101,11 +102,16 @@ export function FreeChatPage() { const anonMode = useAnonymousMode(); const modelSlug = anonMode.isAnonymous ? anonMode.modelSlug : ""; const resetKey = anonMode.isAnonymous ? anonMode.resetKey : 0; + const webSearchEnabled = anonMode.isAnonymous ? anonMode.webSearchEnabled : true; const [messages, setMessages] = useState([]); const [isRunning, setIsRunning] = useState(false); const [tokenUsageStore] = useState(() => createTokenUsageStore()); const abortControllerRef = useRef(null); + // Mirror the latest messages into a ref so onNew stays a stable callback + // (it reads history on demand instead of depending on the array). + const messagesRef = useRef([]); + messagesRef.current = messages; // Turnstile CAPTCHA state const [captchaRequired, setCaptchaRequired] = useState(false); @@ -152,6 +158,7 @@ export function FreeChatPage() { model_slug: modelSlug, messages: messageHistory, }; + if (!webSearchEnabled) reqBody.disabled_tools = ["web_search"]; if (turnstileToken) reqBody.turnstile_token = turnstileToken; const response = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/stream`, { @@ -301,7 +308,7 @@ export function FreeChatPage() { throw err; } }, - [modelSlug, tokenUsageStore] + [modelSlug, tokenUsageStore, webSearchEnabled] ); const onNew = useCallback( @@ -345,7 +352,7 @@ export function FreeChatPage() { }, ]); - const messageHistory = messages + const messageHistory = messagesRef.current .filter((m) => m.role === "user" || m.role === "assistant") .map((m) => { let text = ""; @@ -395,7 +402,7 @@ export function FreeChatPage() { abortControllerRef.current = null; } }, - [messages, doStream] + [modelSlug, anonMode, doStream] ); /** Called when Turnstile resolves successfully. Stores the token and auto-retries. */ @@ -481,19 +488,21 @@ export function FreeChatPage() {
{captchaRequired && TURNSTILE_SITE_KEY && ( -
-
- - Quick verification to continue chatting -
- turnstileRef.current?.reset()} - onExpire={() => turnstileRef.current?.reset()} - options={{ theme: "auto", size: "normal" }} - /> +
+ + + Quick verification to continue chatting + + turnstileRef.current?.reset()} + onExpire={() => turnstileRef.current?.reset()} + options={{ theme: "auto", size: "normal" }} + /> + +
)} diff --git a/surfsense_web/components/free-chat/free-composer.tsx b/surfsense_web/components/free-chat/free-composer.tsx index 943ace9aa..46d9e0259 100644 --- a/surfsense_web/components/free-chat/free-composer.tsx +++ b/surfsense_web/components/free-chat/free-composer.tsx @@ -6,6 +6,7 @@ import { type FC, useCallback, useRef, useState } from "react"; import { toast } from "sonner"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; @@ -71,10 +72,11 @@ export const FreeComposer: FC = () => { const { gate } = useLoginGate(); const anonMode = useAnonymousMode(); const [text, setText] = useState(""); - const [webSearchEnabled, setWebSearchEnabled] = useState(true); const fileInputRef = useRef(null); const hasUploadedDoc = anonMode.isAnonymous && anonMode.uploadedDoc !== null; + const webSearchEnabled = anonMode.isAnonymous ? anonMode.webSearchEnabled : true; + const setWebSearchEnabled = anonMode.isAnonymous ? anonMode.setWebSearchEnabled : () => {}; const handleTextChange = useCallback( (e: React.ChangeEvent) => { @@ -189,14 +191,11 @@ export const FreeComposer: FC = () => { @@ -207,13 +206,13 @@ export const FreeComposer: FC = () => { -
+ diff --git a/surfsense_web/components/free-chat/free-model-selector.tsx b/surfsense_web/components/free-chat/free-model-selector.tsx index b7ae60bd3..9bf4ecee5 100644 --- a/surfsense_web/components/free-chat/free-model-selector.tsx +++ b/surfsense_web/components/free-chat/free-model-selector.tsx @@ -1,10 +1,18 @@ "use client"; -import { Bot, Check, ChevronDown, Search } from "lucide-react"; +import { Bot, Check, ChevronDown } from "lucide-react"; import { useRouter } from "next/navigation"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; import type { AnonModel } from "@/contracts/types/anonymous-chat.types"; @@ -19,21 +27,18 @@ export function FreeModelSelector({ className }: { className?: string }) { const [open, setOpen] = useState(false); const [models, setModels] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [focusedIndex, setFocusedIndex] = useState(-1); - const searchInputRef = useRef(null); useEffect(() => { - anonymousChatApiService.getModels().then(setModels).catch(console.error); - }, []); - - const handleOpenChange = useCallback((next: boolean) => { - if (next) { - setSearchQuery(""); - setFocusedIndex(-1); - requestAnimationFrame(() => searchInputRef.current?.focus()); - } - setOpen(next); + const controller = new AbortController(); + anonymousChatApiService + .getModels() + .then((data) => { + if (!controller.signal.aborted) setModels(data); + }) + .catch((err) => { + if (!controller.signal.aborted) console.error(err); + }); + return () => controller.abort(); }, []); const currentModel = useMemo( @@ -41,22 +46,12 @@ export function FreeModelSelector({ className }: { className?: string }) { [models, currentSlug] ); + // Free models first, premium last; immutable sort to avoid mutating state. const sortedModels = useMemo( - () => [...models].sort((a, b) => Number(a.is_premium) - Number(b.is_premium)), + () => models.toSorted((a, b) => Number(a.is_premium) - Number(b.is_premium)), [models] ); - const filteredModels = useMemo(() => { - if (!searchQuery.trim()) return sortedModels; - const q = searchQuery.toLowerCase(); - return sortedModels.filter( - (m) => - m.name.toLowerCase().includes(q) || - m.model_name.toLowerCase().includes(q) || - m.provider.toLowerCase().includes(q) - ); - }, [sortedModels, searchQuery]); - const handleSelect = useCallback( (model: AnonModel) => { setOpen(false); @@ -70,42 +65,15 @@ export function FreeModelSelector({ className }: { className?: string }) { [currentSlug, anonMode, router] ); - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - const count = filteredModels.length; - if (count === 0) return; - switch (e.key) { - case "ArrowDown": - e.preventDefault(); - setFocusedIndex((p) => (p < count - 1 ? p + 1 : 0)); - break; - case "ArrowUp": - e.preventDefault(); - setFocusedIndex((p) => (p > 0 ? p - 1 : count - 1)); - break; - case "Enter": - e.preventDefault(); - if (focusedIndex >= 0 && focusedIndex < count) { - handleSelect(filteredModels[focusedIndex]); - } - break; - } - }, - [filteredModels, focusedIndex, handleSelect] - ); - return ( - + - e.preventDefault()} - > -
- - setSearchQuery(e.target.value)} - onKeyDown={handleKeyDown} - className="w-full pl-8 pr-3 py-2.5 text-sm bg-transparent focus:outline-none placeholder:text-muted-foreground" - /> -
-
- {filteredModels.length === 0 ? ( -
- -

No models found

-
- ) : ( - filteredModels.map((model, index) => { - const isSelected = model.seo_slug === currentSlug; - const isFocused = focusedIndex === index; - return ( -
handleSelect(model)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - handleSelect(model); - } - }} - onMouseEnter={() => setFocusedIndex(index)} - className={cn( - "group flex items-center gap-2.5 px-3 py-2 rounded-xl cursor-pointer", - "transition-colors duration-150 mx-2", - "hover:bg-accent hover:text-accent-foreground", - isFocused && "bg-accent text-accent-foreground", - isSelected && "bg-accent text-accent-foreground" - )} - > -
- {getProviderIcon(model.provider, { className: "size-5" })} -
-
-
- {model.name} - {model.is_premium ? ( - - Premium - - ) : ( - - Free - - )} + + (value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0)} + > + + + No models found. + + {sortedModels.map((model) => { + const isSelected = model.seo_slug === currentSlug; + return ( + handleSelect(model)} + className="gap-2.5" + > +
+ {getProviderIcon(model.provider, { className: "size-5" })}
- - {model.model_name} - -
- {isSelected && } -
- ); - }) - )} -
+
+
+ {model.name} + + {model.is_premium ? "Premium" : "Free"} + +
+ + {model.model_name} + +
+ {isSelected && } + + ); + })} + + + ); diff --git a/surfsense_web/components/free-chat/free-right-panel.tsx b/surfsense_web/components/free-chat/free-right-panel.tsx index f2b07815f..f37af6142 100644 --- a/surfsense_web/components/free-chat/free-right-panel.tsx +++ b/surfsense_web/components/free-chat/free-right-panel.tsx @@ -4,6 +4,14 @@ import { Lock } from "lucide-react"; import Link from "next/link"; import type { FC } from "react"; import { Button } from "@/components/ui/button"; +import { + Empty, + EmptyContent, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from "@/components/ui/empty"; interface GatedTabProps { title: string; @@ -11,16 +19,20 @@ interface GatedTabProps { } const GatedTab: FC = ({ title, description }) => ( -
-
- -
-

{title}

-

{description}

- -
+ + + + + + {title} + {description} + + + + + ); export const ReportsGatedPlaceholder: FC = () => ( diff --git a/surfsense_web/components/free-chat/quota-bar.tsx b/surfsense_web/components/free-chat/quota-bar.tsx index 0693ef539..6b2368218 100644 --- a/surfsense_web/components/free-chat/quota-bar.tsx +++ b/surfsense_web/components/free-chat/quota-bar.tsx @@ -2,6 +2,7 @@ import { OctagonAlert, Orbit } from "lucide-react"; import Link from "next/link"; +import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { cn } from "@/lib/utils"; @@ -19,38 +20,30 @@ export function QuotaBar({ used, limit, warningThreshold, className }: QuotaBarP const isExceeded = used >= limit; return ( -
-
+
+
{used.toLocaleString()} / {limit.toLocaleString()} tokens {isExceeded ? ( - Limit reached + Limit reached ) : isWarning ? ( - - + + {remaining.toLocaleString()} remaining ) : ( {percentage.toFixed(0)}% )}
- div]:bg-red-500", - isWarning && !isExceeded && "[&>div]:bg-amber-500" - )} - /> + {isExceeded && ( - - - Create free account for 5M more tokens - + )}
); diff --git a/surfsense_web/components/free-chat/quota-warning-banner.tsx b/surfsense_web/components/free-chat/quota-warning-banner.tsx index 828e8006e..e6aa89d42 100644 --- a/surfsense_web/components/free-chat/quota-warning-banner.tsx +++ b/surfsense_web/components/free-chat/quota-warning-banner.tsx @@ -3,6 +3,7 @@ import { OctagonAlert, Orbit, X } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -27,61 +28,46 @@ export function QuotaWarningBanner({ if (isExceeded) { return ( -
-
- -
-

- Free token limit reached -

-

- You've used all {limit.toLocaleString()} free tokens. Create a free account to - get $5 of premium credit and access to all models. -

- - + + + Free token limit reached + +

+ You've used all {limit.toLocaleString()} free tokens. Create a free account to get + $5 of premium credit and access to all models. +

+
-
-
+ + + ); } return ( -
-
- -

- You've used {used.toLocaleString()} of {limit.toLocaleString()} free tokens.{" "} - - Create an account - {" "} - for $5 of premium credit. -

- -
-
+ + + Running low on free tokens + + You've used {used.toLocaleString()} of {limit.toLocaleString()} free tokens.{" "} + + Create an account + {" "} + for $5 of premium credit. + + + ); } diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 881fbe2b0..a90d6b32e 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -89,10 +89,7 @@ const DesktopLocalTabContent = dynamic( { ssr: false } ); -const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = [ - "USER_MEMORY", - "TEAM_MEMORY", -]; +const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = ["USER_MEMORY", "TEAM_MEMORY"]; const MEMORY_DOCUMENTS: DocumentNodeDoc[] = [ { id: -1001, diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 769327e1e..43a5cad74 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -220,13 +220,7 @@ export const DocumentMentionPicker = forwardRef< DocumentMentionPickerRef, DocumentMentionPickerProps >(function DocumentMentionPicker( - { - searchSpaceId, - onSelectionChange, - onDone, - initialSelectedDocuments = [], - externalSearch = "", - }, + { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "" }, ref ) { const search = externalSearch; diff --git a/surfsense_web/components/ui/empty.tsx b/surfsense_web/components/ui/empty.tsx new file mode 100644 index 000000000..79145502f --- /dev/null +++ b/surfsense_web/components/ui/empty.tsx @@ -0,0 +1,94 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +function Empty({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +const emptyMediaVariants = cva( + "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-transparent", + icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +function EmptyMedia({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +
a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", + className + )} + {...props} + /> + ); +} + +function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia }; diff --git a/surfsense_web/contexts/anonymous-mode.tsx b/surfsense_web/contexts/anonymous-mode.tsx index eaf0996c3..c24a5cb1b 100644 --- a/surfsense_web/contexts/anonymous-mode.tsx +++ b/surfsense_web/contexts/anonymous-mode.tsx @@ -9,6 +9,8 @@ export interface AnonymousModeContextValue { setModelSlug: (slug: string) => void; uploadedDoc: { filename: string; sizeBytes: number } | null; setUploadedDoc: (doc: { filename: string; sizeBytes: number } | null) => void; + webSearchEnabled: boolean; + setWebSearchEnabled: (enabled: boolean) => void; resetKey: number; resetChat: () => void; } @@ -34,6 +36,7 @@ export function AnonymousModeProvider({ const [uploadedDoc, setUploadedDoc] = useState<{ filename: string; sizeBytes: number } | null>( null ); + const [webSearchEnabled, setWebSearchEnabled] = useState(true); const [resetKey, setResetKey] = useState(0); const resetChat = () => setResetKey((k) => k + 1); @@ -56,10 +59,12 @@ export function AnonymousModeProvider({ setModelSlug, uploadedDoc, setUploadedDoc, + webSearchEnabled, + setWebSearchEnabled, resetKey, resetChat, }), - [modelSlug, uploadedDoc, resetKey] + [modelSlug, uploadedDoc, webSearchEnabled, resetKey] ); return {children}; From 2dc4f6ade43b139297845af342b074d34bf22eec Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:14:01 -0700 Subject: [PATCH 123/133] feat(hero-section): restructure hero section with new use case categories and enhanced UI components - Introduced new use case categories for better organization of features. - Updated the hero section to include additional use cases with detailed descriptions and media sources. - Added new UI components such as Empty states, ScrollArea, and Tabs for improved user interaction. - Refactored existing use cases to align with the new structure and enhance clarity. --- .../components/homepage/hero-section.tsx | 605 ++++++++++++------ 1 file changed, 408 insertions(+), 197 deletions(-) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index c0abd927f..def75b94e 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -1,6 +1,6 @@ "use client"; -import { ChevronDown, Download, Monitor } from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; +import { ChevronDown, Clock, Download, Monitor, Sparkles } from "lucide-react"; +import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import Balancer from "react-wrap-balancer"; @@ -11,7 +11,18 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from "@/components/ui/empty"; import { ExpandedMediaOverlay, useExpandedMedia } from "@/components/ui/expanded-gif-overlay"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { GITHUB_RELEASES_URL, @@ -50,96 +61,193 @@ const GoogleLogo = ({ className }: { className?: string }) => ( ); -const TAB_ITEMS = [ +type HeroUseCase = { + id: string; + title: string; + description: string; + src: string | null; + comingSoon?: boolean; +}; + +type HeroCategory = { + id: string; + label: string; + desktopOnly?: boolean; + useCases: HeroUseCase[]; +}; + +const HERO_TUTORIAL = "/homepage/hero_tutorial"; +const HERO_REALTIME = "/homepage/hero_realtime"; + +const CATEGORIES: HeroCategory[] = [ { - title: "General Assist", - description: "Launch SurfSense instantly from any application.", - src: "/homepage/hero_tutorial/general_assist.mp4", - featured: true, + id: "desktop", + label: "Desktop App", + desktopOnly: true, + useCases: [ + { + id: "general", + title: "General Assist", + description: "Launch SurfSense instantly from any application with a global shortcut.", + src: `${HERO_TUTORIAL}/general_assist.mp4`, + }, + { + id: "quick", + title: "Quick Assist", + description: "Select text anywhere, then ask AI to explain, rewrite, or act on it.", + src: `${HERO_TUTORIAL}/quick_assist.mp4`, + }, + { + id: "screenshot", + title: "Screenshot Assist", + description: "Capture any region of your screen and ask AI about what’s in it.", + src: `${HERO_TUTORIAL}/screenshot_assist.mp4`, + }, + { + id: "watch-folder", + title: "Watch Local Folder", + description: "Auto-sync a local folder to your knowledge base. Great for Obsidian vaults.", + src: `${HERO_TUTORIAL}/folder_watch.mp4`, + }, + ], }, { - title: "Quick Assist", - description: "Select text anywhere, then ask AI to explain, rewrite, or act on it.", - src: "/homepage/hero_tutorial/quick_assist.mp4", - featured: true, + id: "deliverables", + label: "Deliverable Studio", + useCases: [ + { + id: "report", + title: "AI Report Generator", + description: + "Generate cited research reports from your documents, then export to PDF or Markdown.", + src: `${HERO_TUTORIAL}/ReportGenGif_compressed.mp4`, + }, + { + id: "podcast", + title: "AI Podcast Generator", + description: "Turn any document or folder into a two-host AI podcast in under 20 seconds.", + src: `${HERO_TUTORIAL}/PodcastGenGif.mp4`, + }, + { + id: "presentation", + title: "AI Presentation & Video Maker", + description: "Create editable slide decks and narrated video overviews from your sources.", + src: `${HERO_TUTORIAL}/video_gen_surf.mp4`, + }, + { + id: "image", + title: "AI Image Generator", + description: "Generate high-quality images straight from your chats and documents.", + src: `${HERO_TUTORIAL}/ImageGenGif.mp4`, + }, + { + id: "resume", + title: "AI Resume Builder", + description: "Draft and format an ATS-ready resume as a polished PDF.", + src: null, + comingSoon: true, + }, + ], }, { - title: "Screenshot Assist", - description: - "Use a global shortcut to select a region on your screen and attach it to your chat message.", - src: "/homepage/hero_tutorial/screenshot_assist.mp4", - featured: true, + id: "automations", + label: "Automations", + useCases: [ + { + id: "sort", + title: "AI Document Sorting", + description: "Let AI automatically organize files into the right folders as they arrive.", + src: null, + comingSoon: true, + }, + { + id: "schedule", + title: "Scheduled AI Workflows", + description: "Run an agent on a schedule: daily briefs, weekly digests, recurring reports.", + src: null, + comingSoon: true, + }, + { + id: "event", + title: "Event-Triggered Automations", + description: + "Fire an agent the moment a document lands in a folder, then post the result to your tools.", + src: null, + comingSoon: true, + }, + { + id: "chat-built", + title: "Chat-Built Automations", + description: "Describe an automation in plain English and SurfSense builds it for you.", + src: null, + comingSoon: true, + }, + ], }, { - title: "Watch Local Folder", - description: - "Watch a local folder and automatically sync file changes to your knowledge base. Works great with Obsidian vaults.", - src: "/homepage/hero_tutorial/folder_watch.mp4", - featured: true, - }, - // { - // title: "Connect & Sync", - // description: - // "Connect data sources like Notion, Drive and Gmail. Automatically sync to keep them updated.", - // src: "/homepage/hero_tutorial/ConnectorFlowGif.mp4", - // featured: true, - // }, - // { - // title: "Upload Documents", - // description: "Upload documents directly, from images to massive PDFs.", - // src: "/homepage/hero_tutorial/DocUploadGif.mp4", - // featured: true, - // }, - { - title: "Video & Presentations", - description: - "Create short videos and editable presentations with AI-generated visuals and narration from your sources.", - src: "/homepage/hero_tutorial/video_gen_surf.mp4", - featured: false, + id: "search-chat", + label: "Search & Chat", + useCases: [ + { + id: "chat-docs", + title: "Chat With Your PDFs & Docs", + description: "Ask questions across all your files and get answers with inline citations.", + src: `${HERO_TUTORIAL}/BQnaGif_compressed.mp4`, + }, + { + id: "search", + title: "AI Search With Citations", + description: "Hybrid semantic and keyword search across your entire knowledge base.", + src: `${HERO_TUTORIAL}/BSNCGif.mp4`, + }, + { + id: "collab", + title: "Collaborative AI Chat", + description: "Work on AI conversations with your team in real time.", + src: `${HERO_REALTIME}/RealTimeChatGif.mp4`, + }, + { + id: "comments", + title: "Comments & Mentions", + description: "Comment and tag teammates on any AI message.", + src: `${HERO_REALTIME}/RealTimeCommentsFlow.mp4`, + }, + ], }, { - title: "Search & Citation", - description: "Ask questions and get cited responses from your knowledge base.", - src: "/homepage/hero_tutorial/BSNCGif.mp4", - featured: false, + id: "connectors", + label: "Connectors & Integrations", + useCases: [ + { + id: "connect", + title: "Connect & Sync Your Tools", + description: + "Sync Notion, Slack, Google Drive, Gmail, GitHub, Linear and 25+ sources into one searchable corpus.", + src: `${HERO_TUTORIAL}/ConnectorFlowGif.mp4`, + }, + { + id: "upload", + title: "Chat With Uploaded Files", + description: "Drop in PDFs, Office docs, images and audio. Instantly searchable.", + src: `${HERO_TUTORIAL}/DocUploadGif.mp4`, + }, + { + id: "write-back", + title: "Connector Write-Back", + description: "Let the agent post results back to Notion, Slack, Linear and Drive.", + src: null, + comingSoon: true, + }, + { + id: "obsidian", + title: "Obsidian & Knowledge Base Sync", + description: "Keep your Obsidian vault and personal knowledge base in sync.", + src: null, + comingSoon: true, + }, + ], }, - { - title: "Document Q&A", - description: "Mention specific documents in chat for targeted answers.", - src: "/homepage/hero_tutorial/BQnaGif_compressed.mp4", - featured: false, - }, - { - title: "Reports", - description: "Generate reports from your sources in many formats.", - src: "/homepage/hero_tutorial/ReportGenGif_compressed.mp4", - featured: false, - }, - { - title: "Podcasts", - description: "Turn anything into a podcast in under 20 seconds.", - src: "/homepage/hero_tutorial/PodcastGenGif.mp4", - featured: false, - }, - { - title: "Image Generation", - description: "Generate high-quality images easily from your conversations.", - src: "/homepage/hero_tutorial/ImageGenGif.mp4", - featured: false, - }, - { - title: "Collaborative Chat", - description: "Collaborate on AI-powered conversations in realtime with your team.", - src: "/homepage/hero_realtime/RealTimeChatGif.mp4", - featured: false, - }, - { - title: "Comments", - description: "Add comments and tag teammates on any message.", - src: "/homepage/hero_realtime/RealTimeCommentsFlow.mp4", - featured: false, - }, -] as const; +]; export function HeroSection() { return ( @@ -279,117 +387,15 @@ function DownloadButton() { ); } -const BrowserWindow = () => { - const [selectedIndex, setSelectedIndex] = useState(0); - const selectedItem = TAB_ITEMS[selectedIndex]; - const { expanded, open, close } = useExpandedMedia(); - - return ( - <> - -
-
-
-
-
-
-
- {TAB_ITEMS.map((item, index) => ( - - - {index !== TAB_ITEMS.length - 1 && ( -
- )} - - ))} -
-
-
- - -
-
-

- {selectedItem.title} -

-

- {selectedItem.description} -

-
-
- -
-
-
- - - - {expanded && ( - - )} - - - ); -}; - -const TabVideo = memo(function TabVideo({ src }: { src: string }) { +const TabVideo = memo(function TabVideo({ + src, + title, + reduceMotion, +}: { + src: string; + title: string; + reduceMotion: boolean; +}) { const videoRef = useRef(null); const [hasLoaded, setHasLoaded] = useState(false); @@ -398,8 +404,11 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { const video = videoRef.current; if (!video) return; video.currentTime = 0; - video.play().catch(() => {}); - }, []); + // Respect reduced-motion: show the first frame and expose controls instead of autoplaying. + if (!reduceMotion) { + video.play().catch(() => {}); + } + }, [reduceMotion]); const handleCanPlay = useCallback(() => { setHasLoaded(true); @@ -411,7 +420,10 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { ref={videoRef} key={src} src={src} - preload="auto" + preload={reduceMotion ? "metadata" : "auto"} + aria-label={`${title} demo`} + autoPlay={!reduceMotion} + controls={reduceMotion} loop muted playsInline @@ -419,8 +431,207 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { className="aspect-video w-full rounded-lg sm:rounded-xl" /> {!hasLoaded && ( -
+ )}
); }); + +const UseCasePlaceholder = ({ title }: { title: string }) => ( + + + + + Demo coming soon + {`A walkthrough of ${title} is on the way.`} + + +); + +const DesktopBadge = () => ( + + + + + + Desktop app only + +); + +const UseCasePane = memo(function UseCasePane({ + useCase, + reduceMotion, +}: { + useCase: HeroUseCase; + reduceMotion: boolean; +}) { + const { expanded, open, close } = useExpandedMedia(); + const hasVideo = !useCase.comingSoon && Boolean(useCase.src); + + const media = hasVideo ? ( + + ) : ( +
+
+ +
+
+ ); + + const card = ( +
+
+
+

+ {useCase.title} +

+

+ {useCase.description} +

+
+
+ {media} +
+ ); + + return ( + <> + {reduceMotion ? ( + card + ) : ( + + {card} + + )} + + + {expanded && hasVideo && ( + + )} + + + ); +}); + +const CategoryPanel = memo(function CategoryPanel({ + category, + reduceMotion, +}: { + category: HeroCategory; + reduceMotion: boolean; +}) { + return ( +
+ {category.desktopOnly && ( +
+
+ )} + + + + {category.useCases.map((useCase) => ( + + {useCase.title} + + ))} + + + +
+ {category.useCases.map((useCase) => ( + + + + ))} +
+
+
+ ); +}); + +const BrowserWindow = () => { + const [activeCategory, setActiveCategory] = useState(CATEGORIES[0].id); + const reduceMotion = useReducedMotion() ?? false; + + return ( + +
+
+
+
+
+
+ + + {CATEGORIES.map((category, index) => ( + + + {category.label} + {category.desktopOnly && } + + {index !== CATEGORIES.length - 1 && ( + + )} + + ))} + + + +
+
+ {CATEGORIES.map((category) => ( + + + + ))} +
+ + ); +}; From 891d7b2a88b2fe0342c93abbba619b20e6707165 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:21:24 -0700 Subject: [PATCH 124/133] feat(docs): update README files with new use case categories and detailed descriptions - Enhanced the structure of the README files across multiple languages to include new use case categories. - Added detailed descriptions and media for features such as AI Report Generator, AI Podcast Generator, and more. - Improved clarity and organization of existing features, ensuring consistency across all language versions. - Introduced upcoming features and automation capabilities, highlighting their benefits and functionalities. --- README.es.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.hi.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.pt-BR.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.zh-CN.md | 70 ++++++++++++++++++++++++++++++++++++------------- 5 files changed, 260 insertions(+), 90 deletions(-) diff --git a/README.es.md b/README.es.md index dea86a793..01bf95993 100644 --- a/README.es.md +++ b/README.es.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. Una vez que todo esté indexado, pregunta lo que quieras (Casos de uso): - - Aplicación de Escritorio — General Assist + **Aplicación de Escritorio** (extras nativos, además de todo lo de abajo, no un conjunto aparte) + + - General Assist: abre SurfSense al instante desde cualquier aplicación con un atajo global.

General Assist

- - Aplicación de Escritorio — Quick Assist + - Quick Assist: selecciona texto en cualquier lugar y pide a la IA que lo explique, reescriba o actúe sobre él.

Quick Assist

- - Aplicación de Escritorio — Screenshot Assist + - Screenshot Assist: captura cualquier región de tu pantalla y pregunta a la IA sobre lo que contiene.

Screenshot Assist

- - Aplicación de Escritorio — Watch Local Folder + - Watch Local Folder: sincroniza automáticamente una carpeta local con tu base de conocimiento. Ideal para bóvedas de Obsidian.

Watch Local Folder

- - Generación de videos + **Estudio de Entregables** -

Generación de Videos

+ - AI Report Generator: genera informes de investigación con citas y expórtalos a PDF, DOCX, HTML, LaTeX, EPUB, ODT o texto plano. - - Búsqueda básica y citaciones +

AI Report Generator

-

Búsqueda y Citación

+ - AI Podcast Generator: convierte cualquier documento o carpeta en un pódcast de IA con dos presentadores en menos de 20 segundos. - - QNA con mención de documentos +

AI Podcast Generator

-

QNA con Mención de Documentos

-

QNA con Mención de Documentos

+ - AI Presentation & Video Maker: crea presentaciones editables y videos narrados a partir de tus fuentes. - - Generación de informes y exportaciones (PDF, DOCX, HTML, LaTeX, EPUB, ODT, texto plano) +

AI Presentation and Video Maker

-

Generación de Informes

+ - AI Image Generator: genera imágenes de alta calidad directamente desde tus chats y documentos. - - Generación de podcasts +

AI Image Generator

-

Generación de Podcasts

+ - AI Resume Builder: redacta y da formato a un currículum listo para ATS en un PDF pulido. (próximamente) - - Generación de imágenes + **Búsqueda y Chat** -

Generación de Imágenes

+ - Chat With Your PDFs & Docs: haz preguntas sobre todos tus archivos y obtén respuestas con citas en línea. - - Y más próximamente. +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: búsqueda híbrida semántica y por palabras clave en toda tu base de conocimiento. + +

AI Search With Citations

+ + - Collaborative AI Chat: trabaja en conversaciones de IA con tu equipo en tiempo real. + +

Collaborative AI Chat

+ + - Comments & Mentions: comenta y menciona a tus compañeros en cualquier mensaje de IA. + +

Comments and Mentions

+ + **Conectores e Integraciones** + + - Connect & Sync Your Tools: sincroniza Notion, Slack, Google Drive, Gmail, GitHub, Linear y más de 25 fuentes en un único corpus consultable. + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: sube PDFs, documentos de Office, imágenes y audio. Consultables al instante. + +

Chat With Uploaded Files

+ + - Connector Write-Back: deja que el agente publique los resultados de vuelta en Notion, Slack, Linear y Drive. (próximamente) + + - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. (próximamente) + + **Automatizaciones** + + - AI Document Sorting: deja que la IA organice automáticamente los archivos en las carpetas correctas a medida que llegan. (próximamente) + - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. (próximamente) + - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. (próximamente) + - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. (próximamente) ### Auto-Hospedado diff --git a/README.hi.md b/README.hi.md index 43e24c3ee..c75f9453a 100644 --- a/README.hi.md +++ b/README.hi.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. सब कुछ इंडेक्स हो जाने के बाद, कुछ भी पूछें (उपयोग के मामले): - - डेस्कटॉप ऐप — General Assist + **डेस्कटॉप ऐप** (नीचे दी गई सभी सुविधाओं के अलावा नेटिव एक्स्ट्रा, कोई अलग सेट नहीं) + + - General Assist: किसी भी ऐप्लिकेशन से ग्लोबल शॉर्टकट के ज़रिए SurfSense तुरंत खोलें।

General Assist

- - डेस्कटॉप ऐप — Quick Assist + - Quick Assist: कहीं भी टेक्स्ट चुनें और AI से उसे समझाने, दोबारा लिखने या उस पर कार्रवाई करने को कहें।

Quick Assist

- - डेस्कटॉप ऐप — Screenshot Assist + - Screenshot Assist: अपनी स्क्रीन का कोई भी हिस्सा कैप्चर करें और AI से उसमें मौजूद चीज़ों के बारे में पूछें।

Screenshot Assist

- - डेस्कटॉप ऐप — Watch Local Folder + - Watch Local Folder: किसी लोकल फ़ोल्डर को अपने नॉलेज बेस के साथ अपने-आप सिंक करें। Obsidian vaults के लिए बढ़िया।

Watch Local Folder

- - वीडियो जनरेशन + **डिलीवरेबल स्टूडियो** -

वीडियो जनरेशन

+ - AI Report Generator: उद्धरण सहित रिसर्च रिपोर्ट बनाएं और PDF, DOCX, HTML, LaTeX, EPUB, ODT या सादे टेक्स्ट में एक्सपोर्ट करें। - - बेसिक सर्च और उद्धरण +

AI Report Generator

-

सर्च और उद्धरण

+ - AI Podcast Generator: किसी भी दस्तावेज़ या फ़ोल्डर को 20 सेकंड से भी कम में दो-होस्ट वाले AI पॉडकास्ट में बदलें। - - दस्तावेज़ मेंशन QNA +

AI Podcast Generator

-

दस्तावेज़ मेंशन QNA

-

दस्तावेज़ मेंशन QNA

+ - AI Presentation & Video Maker: अपने स्रोतों से एडिट करने योग्य स्लाइड डेक और नैरेटेड वीडियो बनाएं। - - रिपोर्ट जनरेशन और एक्सपोर्ट (PDF, DOCX, HTML, LaTeX, EPUB, ODT, सादा टेक्स्ट) +

AI Presentation and Video Maker

-

रिपोर्ट जनरेशन

+ - AI Image Generator: अपनी चैट और दस्तावेज़ों से सीधे उच्च-गुणवत्ता वाली इमेज बनाएं। - - पॉडकास्ट जनरेशन +

AI Image Generator

-

पॉडकास्ट जनरेशन

+ - AI Resume Builder: ATS-तैयार रिज़्यूमे को एक बेहतरीन PDF के रूप में बनाएं और फ़ॉर्मेट करें। (जल्द आ रहा है) - - इमेज जनरेशन + **सर्च और चैट** -

इमेज जनरेशन

+ - Chat With Your PDFs & Docs: अपनी सभी फ़ाइलों पर सवाल पूछें और इनलाइन उद्धरणों के साथ जवाब पाएं। - - और भी बहुत कुछ जल्द आ रहा है। +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: अपने पूरे नॉलेज बेस में हाइब्रिड सेमांटिक और कीवर्ड सर्च। + +

AI Search With Citations

+ + - Collaborative AI Chat: अपनी टीम के साथ रियल टाइम में AI बातचीत पर काम करें। + +

Collaborative AI Chat

+ + - Comments & Mentions: किसी भी AI संदेश पर टिप्पणी करें और टीम के साथियों को टैग करें। + +

Comments and Mentions

+ + **कनेक्टर्स और इंटीग्रेशन** + + - Connect & Sync Your Tools: Notion, Slack, Google Drive, Gmail, GitHub, Linear और 25+ स्रोतों को एक खोजने योग्य कॉर्पस में सिंक करें। + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: PDF, Office दस्तावेज़, इमेज और ऑडियो अपलोड करें। तुरंत खोजने योग्य। + +

Chat With Uploaded Files

+ + - Connector Write-Back: एजेंट को परिणाम वापस Notion, Slack, Linear और Drive में पोस्ट करने दें। (जल्द आ रहा है) + + - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। (जल्द आ रहा है) + + **ऑटोमेशन** + + - AI Document Sorting: फ़ाइलें आते ही AI को उन्हें सही फ़ोल्डरों में अपने-आप व्यवस्थित करने दें। (जल्द आ रहा है) + - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। (जल्द आ रहा है) + - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। (जल्द आ रहा है) + - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। (जल्द आ रहा है) ### सेल्फ-होस्टेड diff --git a/README.md b/README.md index ab9f9e221..c14e1b441 100644 --- a/README.md +++ b/README.md @@ -77,48 +77,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. Once everything is indexed, Ask Away (Use Cases): - - Desktop App — General Assist + **Desktop App** (native extras on top of everything below, not a separate feature set) + + - General Assist: launch SurfSense instantly from any application with a global shortcut.

General Assist

- - Desktop App — Quick Assist + - Quick Assist: select text anywhere, then ask AI to explain, rewrite, or act on it.

Quick Assist

- - Desktop App — Screenshot Assist + - Screenshot Assist: capture any region of your screen and ask AI about what's in it.

Screenshot Assist

- - Desktop App — Watch Local Folder + - Watch Local Folder: auto-sync a local folder to your knowledge base. Great for Obsidian vaults.

Watch Local Folder

- - Video Generation + **Deliverable Studio** -

Video Generation

+ - AI Report Generator: generate cited research reports and export to PDF, DOCX, HTML, LaTeX, EPUB, ODT, or plain text. - - Basic search and citation +

AI Report Generator

-

Search and Citation

+ - AI Podcast Generator: turn any document or folder into a two-host AI podcast in under 20 seconds. - - Document Mention QNA +

AI Podcast Generator

-

Document Mention QNA

-

Document Mention QNA

+ - AI Presentation & Video Maker: create editable slide decks and narrated video overviews from your sources. - - Report Generations and Exports (PDF, DOCX, HTML, LaTeX, EPUB, ODT, Plain Text) +

AI Presentation and Video Maker

-

Report Generation

+ - AI Image Generator: generate high-quality images straight from your chats and documents. - - Podcast Generations +

AI Image Generator

-

Podcast Generation

+ - AI Resume Builder: draft and format an ATS-ready resume as a polished PDF. (coming soon) - - Image Generations + **Search & Chat** -

Image Generation

+ - Chat With Your PDFs & Docs: ask questions across all your files and get answers with inline citations. - - And more coming soon. +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: hybrid semantic and keyword search across your entire knowledge base. + +

AI Search With Citations

+ + - Collaborative AI Chat: work on AI conversations with your team in real time. + +

Collaborative AI Chat

+ + - Comments & Mentions: comment and tag teammates on any AI message. + +

Comments and Mentions

+ + **Connectors & Integrations** + + - Connect & Sync Your Tools: sync Notion, Slack, Google Drive, Gmail, GitHub, Linear and 25+ sources into one searchable corpus. + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: drop in PDFs, Office docs, images and audio. Instantly searchable. + +

Chat With Uploaded Files

+ + - Connector Write-Back: let the agent post results back to Notion, Slack, Linear and Drive. (coming soon) + + - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. (coming soon) + + **Automations** + + - AI Document Sorting: let AI automatically organize files into the right folders as they arrive. (coming soon) + - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. (coming soon) + - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. (coming soon) + - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. (coming soon) ### Self Hosted diff --git a/README.pt-BR.md b/README.pt-BR.md index fcb004cd6..e65b9bbdd 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. Quando tudo estiver indexado, pergunte o que quiser (Casos de uso): - - Aplicativo Desktop — General Assist + **Aplicativo Desktop** (extras nativos, além de tudo o que está abaixo, não um conjunto separado) + + - General Assist: abra o SurfSense instantaneamente de qualquer aplicativo com um atalho global.

General Assist

- - Aplicativo Desktop — Quick Assist + - Quick Assist: selecione um texto em qualquer lugar e peça à IA para explicar, reescrever ou agir sobre ele.

Quick Assist

- - Aplicativo Desktop — Screenshot Assist + - Screenshot Assist: capture qualquer região da tela e pergunte à IA sobre o que está nela.

Screenshot Assist

- - Aplicativo Desktop — Watch Local Folder + - Watch Local Folder: sincronize automaticamente uma pasta local com sua base de conhecimento. Ótimo para cofres do Obsidian.

Watch Local Folder

- - Geração de vídeos + **Estúdio de Entregáveis** -

Geração de Vídeos

+ - AI Report Generator: gere relatórios de pesquisa com citações e exporte para PDF, DOCX, HTML, LaTeX, EPUB, ODT ou texto simples. - - Busca básica e citações +

AI Report Generator

-

Busca e Citação

+ - AI Podcast Generator: transforme qualquer documento ou pasta em um podcast de IA com dois apresentadores em menos de 20 segundos. - - QNA com menção de documentos +

AI Podcast Generator

-

QNA com Menção de Documentos

-

QNA com Menção de Documentos

+ - AI Presentation & Video Maker: crie apresentações editáveis e vídeos narrados a partir das suas fontes. - - Geração de relatórios e exportações (PDF, DOCX, HTML, LaTeX, EPUB, ODT, texto simples) +

AI Presentation and Video Maker

-

Geração de Relatórios

+ - AI Image Generator: gere imagens de alta qualidade diretamente das suas conversas e documentos. - - Geração de podcasts +

AI Image Generator

-

Geração de Podcasts

+ - AI Resume Builder: monte e formate um currículo pronto para ATS em um PDF caprichado. (em breve) - - Geração de imagens + **Busca e Chat** -

Geração de Imagens

+ - Chat With Your PDFs & Docs: faça perguntas sobre todos os seus arquivos e receba respostas com citações inline. - - E mais em breve. +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: busca híbrida semântica e por palavra-chave em toda a sua base de conhecimento. + +

AI Search With Citations

+ + - Collaborative AI Chat: trabalhe em conversas de IA com sua equipe em tempo real. + +

Collaborative AI Chat

+ + - Comments & Mentions: comente e marque colegas em qualquer mensagem de IA. + +

Comments and Mentions

+ + **Conectores e Integrações** + + - Connect & Sync Your Tools: sincronize Notion, Slack, Google Drive, Gmail, GitHub, Linear e mais de 25 fontes em um único acervo pesquisável. + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: envie PDFs, documentos do Office, imagens e áudio. Pesquisáveis instantaneamente. + +

Chat With Uploaded Files

+ + - Connector Write-Back: deixe o agente publicar os resultados de volta no Notion, Slack, Linear e Drive. (em breve) + + - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. (em breve) + + **Automações** + + - AI Document Sorting: deixe a IA organizar automaticamente os arquivos nas pastas certas conforme eles chegam. (em breve) + - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. (em breve) + - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. (em breve) + - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. (em breve) ### Auto-Hospedado diff --git a/README.zh-CN.md b/README.zh-CN.md index a07f4afdc..e6f7b48f9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. 一切索引完成后,尽管提问(使用场景): - - 桌面应用 — General Assist + **桌面应用**(在以下所有功能之外的原生附加功能,并非独立的功能集) + + - General Assist:通过全局快捷键,从任意应用中即刻打开 SurfSense。

General Assist

- - 桌面应用 — Quick Assist + - Quick Assist:在任意位置选中文本,让 AI 解释、改写或对其执行操作。

Quick Assist

- - 桌面应用 — Screenshot Assist + - Screenshot Assist:截取屏幕上任意区域,并就其中内容向 AI 提问。

Screenshot Assist

- - 桌面应用 — Watch Local Folder + - Watch Local Folder:将本地文件夹自动同步到你的知识库。非常适合 Obsidian 库。

Watch Local Folder

- - 视频生成 + **成果工作室** -

视频生成

+ - AI Report Generator:生成带引用的研究报告,并导出为 PDF、DOCX、HTML、LaTeX、EPUB、ODT 或纯文本。 - - 基本搜索和引用 +

AI Report Generator

-

搜索和引用

+ - AI Podcast Generator:在 20 秒内将任意文档或文件夹转换为双主持人 AI 播客。 - - 文档提及问答 +

AI Podcast Generator

-

文档提及问答

-

文档提及问答

+ - AI Presentation & Video Maker:根据你的资料创建可编辑的幻灯片和带旁白的视频概览。 - - 报告生成和导出(PDF、DOCX、HTML、LaTeX、EPUB、ODT、纯文本) +

AI Presentation and Video Maker

-

报告生成

+ - AI Image Generator:直接从你的聊天和文档生成高质量图像。 - - 播客生成 +

AI Image Generator

-

播客生成

+ - AI Resume Builder:撰写并排版一份适配 ATS 的精美 PDF 简历。(即将推出) - - 图像生成 + **搜索与聊天** -

图像生成

+ - Chat With Your PDFs & Docs:跨所有文件提问,并获得带内联引用的答案。 - - 更多功能即将推出。 +

Chat With Your PDFs and Docs

+ + - AI Search With Citations:在整个知识库中进行语义与关键词的混合搜索。 + +

AI Search With Citations

+ + - Collaborative AI Chat:与团队实时协作处理 AI 对话。 + +

Collaborative AI Chat

+ + - Comments & Mentions:在任意 AI 消息上评论并 @ 你的队友。 + +

Comments and Mentions

+ + **连接器与集成** + + - Connect & Sync Your Tools:将 Notion、Slack、Google Drive、Gmail、GitHub、Linear 等 25+ 数据源同步为一个可搜索的语料库。 + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files:上传 PDF、Office 文档、图像和音频。即刻可搜索。 + +

Chat With Uploaded Files

+ + - Connector Write-Back:让智能体将结果回写到 Notion、Slack、Linear 和 Drive。(即将推出) + + - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。(即将推出) + + **自动化** + + - AI Document Sorting:在文件到达时,让 AI 自动将其整理到正确的文件夹中。(即将推出) + - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。(即将推出) + - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。(即将推出) + - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。(即将推出) ### 自托管 From 8005e6facea5700ca5be8f122ab99f8065109d00 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:25:27 -0700 Subject: [PATCH 125/133] chore(docs): remove AI Document Sorting from README files across multiple languages - Removed the AI Document Sorting feature description from the README files in Spanish, Hindi, Portuguese, Chinese, and English. - Updated the automations section to streamline content and focus on upcoming features. --- README.es.md | 1 - README.hi.md | 1 - README.md | 1 - README.pt-BR.md | 1 - README.zh-CN.md | 1 - surfsense_web/components/homepage/hero-section.tsx | 7 ------- 6 files changed, 12 deletions(-) diff --git a/README.es.md b/README.es.md index 01bf95993..75f499ab0 100644 --- a/README.es.md +++ b/README.es.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **Automatizaciones** - - AI Document Sorting: deja que la IA organice automáticamente los archivos en las carpetas correctas a medida que llegan. (próximamente) - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. (próximamente) - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. (próximamente) - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. (próximamente) diff --git a/README.hi.md b/README.hi.md index c75f9453a..c03e8a21c 100644 --- a/README.hi.md +++ b/README.hi.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **ऑटोमेशन** - - AI Document Sorting: फ़ाइलें आते ही AI को उन्हें सही फ़ोल्डरों में अपने-आप व्यवस्थित करने दें। (जल्द आ रहा है) - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। (जल्द आ रहा है) - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। (जल्द आ रहा है) - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। (जल्द आ रहा है) diff --git a/README.md b/README.md index c14e1b441..93ad51263 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **Automations** - - AI Document Sorting: let AI automatically organize files into the right folders as they arrive. (coming soon) - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. (coming soon) - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. (coming soon) - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. (coming soon) diff --git a/README.pt-BR.md b/README.pt-BR.md index e65b9bbdd..e7112bcaa 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **Automações** - - AI Document Sorting: deixe a IA organizar automaticamente os arquivos nas pastas certas conforme eles chegam. (em breve) - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. (em breve) - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. (em breve) - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. (em breve) diff --git a/README.zh-CN.md b/README.zh-CN.md index e6f7b48f9..cc73fbe6a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **自动化** - - AI Document Sorting:在文件到达时,让 AI 自动将其整理到正确的文件夹中。(即将推出) - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。(即将推出) - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。(即将推出) - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。(即将推出) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index def75b94e..90b0b7c9e 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -153,13 +153,6 @@ const CATEGORIES: HeroCategory[] = [ id: "automations", label: "Automations", useCases: [ - { - id: "sort", - title: "AI Document Sorting", - description: "Let AI automatically organize files into the right folders as they arrive.", - src: null, - comingSoon: true, - }, { id: "schedule", title: "Scheduled AI Workflows", From 6b3e34aae0c073c90de67e8549585cb9bef9b430 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:30:24 -0700 Subject: [PATCH 126/133] chore(hero-section): remove Obsidian feature from hero section - Deleted the Obsidian & Knowledge Base Sync feature from the hero section to streamline content and focus on currently available features. --- surfsense_web/components/homepage/hero-section.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index 90b0b7c9e..8a831d492 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -231,13 +231,6 @@ const CATEGORIES: HeroCategory[] = [ src: null, comingSoon: true, }, - { - id: "obsidian", - title: "Obsidian & Knowledge Base Sync", - description: "Keep your Obsidian vault and personal knowledge base in sync.", - src: null, - comingSoon: true, - }, ], }, ]; From 3b5cc22f94c5bfa157b719221e04732656f7a7d1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:59:49 -0700 Subject: [PATCH 127/133] feat(release): bump version to 0.0.26 - Updated version number to 0.0.26 in VERSION, pyproject.toml, and package.json files for browser, desktop, and web components. - Ensured consistency in versioning across the project. --- VERSION | 2 +- surfsense_backend/pyproject.toml | 2 +- surfsense_backend/uv.lock | 8503 ++++++++--------- surfsense_browser_extension/package.json | 2 +- surfsense_desktop/package.json | 2 +- .../changelog/content/2026-05-31.mdx | 76 + surfsense_web/package.json | 2 +- 7 files changed, 4324 insertions(+), 4265 deletions(-) create mode 100644 surfsense_web/changelog/content/2026-05-31.mdx diff --git a/VERSION b/VERSION index 2678ff8d6..c4475d3bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.25 +0.0.26 diff --git a/surfsense_backend/pyproject.toml b/surfsense_backend/pyproject.toml index 2ed0acca4..51405ec74 100644 --- a/surfsense_backend/pyproject.toml +++ b/surfsense_backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "surf-new-backend" -version = "0.0.25" +version = "0.0.26" description = "SurfSense Backend" requires-python = ">=3.12" dependencies = [ diff --git a/surfsense_backend/uv.lock b/surfsense_backend/uv.lock index ba88153c5..eae54b1d4 100644 --- a/surfsense_backend/uv.lock +++ b/surfsense_backend/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 1 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'win32'", @@ -28,36 +28,36 @@ dependencies = [ { name = "safetensors" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835, upload-time = "2026-03-04T19:34:12.359Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744 }, ] [[package]] name = "addict" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186, upload-time = "2020-11-21T16:21:31.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832 }, ] [[package]] name = "aiofiles" version = "24.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 }, ] [[package]] name = "aiohappyeyeballs" version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, ] [[package]] @@ -73,76 +73,76 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38", size = 7859748, upload-time = "2026-03-28T17:19:40.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38", size = 7859748 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/bd/ede278648914cabbabfdf95e436679b5d4156e417896a9b9f4587169e376/aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360", size = 752158, upload-time = "2026-03-28T17:16:06.901Z" }, - { url = "https://files.pythonhosted.org/packages/90/de/581c053253c07b480b03785196ca5335e3c606a37dc73e95f6527f1591fe/aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d", size = 501037, upload-time = "2026-03-28T17:16:08.82Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/a5ede193c08f13cc42c0a5b50d1e246ecee9115e4cf6e900d8dbd8fd6acb/aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c", size = 501556, upload-time = "2026-03-28T17:16:10.63Z" }, - { url = "https://files.pythonhosted.org/packages/d6/10/88ff67cd48a6ec36335b63a640abe86135791544863e0cfe1f065d6cef7a/aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97", size = 1757314, upload-time = "2026-03-28T17:16:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/8b/15/fdb90a5cf5a1f52845c276e76298c75fbbcc0ac2b4a86551906d54529965/aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576", size = 1731819, upload-time = "2026-03-28T17:16:14.558Z" }, - { url = "https://files.pythonhosted.org/packages/ec/df/28146785a007f7820416be05d4f28cc207493efd1e8c6c1068e9bdc29198/aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab", size = 1793279, upload-time = "2026-03-28T17:16:16.594Z" }, - { url = "https://files.pythonhosted.org/packages/10/47/689c743abf62ea7a77774d5722f220e2c912a77d65d368b884d9779ef41b/aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d", size = 1891082, upload-time = "2026-03-28T17:16:18.71Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b6/f7f4f318c7e58c23b761c9b13b9a3c9b394e0f9d5d76fbc6622fa98509f6/aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e", size = 1773938, upload-time = "2026-03-28T17:16:21.125Z" }, - { url = "https://files.pythonhosted.org/packages/aa/06/f207cb3121852c989586a6fc16ff854c4fcc8651b86c5d3bd1fc83057650/aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3", size = 1579548, upload-time = "2026-03-28T17:16:23.588Z" }, - { url = "https://files.pythonhosted.org/packages/6c/58/e1289661a32161e24c1fe479711d783067210d266842523752869cc1d9c2/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83", size = 1714669, upload-time = "2026-03-28T17:16:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/3e86d039438a74a86e6a948a9119b22540bae037d6ba317a042ae3c22711/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763", size = 1754175, upload-time = "2026-03-28T17:16:28.18Z" }, - { url = "https://files.pythonhosted.org/packages/f4/30/e717fc5df83133ba467a560b6d8ef20197037b4bb5d7075b90037de1018e/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9", size = 1762049, upload-time = "2026-03-28T17:16:30.941Z" }, - { url = "https://files.pythonhosted.org/packages/e4/28/8f7a2d4492e336e40005151bdd94baf344880a4707573378579f833a64c1/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758", size = 1570861, upload-time = "2026-03-28T17:16:32.953Z" }, - { url = "https://files.pythonhosted.org/packages/78/45/12e1a3d0645968b1c38de4b23fdf270b8637735ea057d4f84482ff918ad9/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9", size = 1790003, upload-time = "2026-03-28T17:16:35.468Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/60374e18d590de16dcb39d6ff62f39c096c1b958e6f37727b5870026ea30/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d", size = 1737289, upload-time = "2026-03-28T17:16:38.187Z" }, - { url = "https://files.pythonhosted.org/packages/02/bf/535e58d886cfbc40a8b0013c974afad24ef7632d645bca0b678b70033a60/aiohttp-3.13.4-cp312-cp312-win32.whl", hash = "sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791", size = 434185, upload-time = "2026-03-28T17:16:40.735Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1a/d92e3325134ebfff6f4069f270d3aac770d63320bd1fcd0eca023e74d9a8/aiohttp-3.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77", size = 461285, upload-time = "2026-03-28T17:16:42.713Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538", size = 745744, upload-time = "2026-03-28T17:16:44.67Z" }, - { url = "https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e", size = 498178, upload-time = "2026-03-28T17:16:46.766Z" }, - { url = "https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3", size = 498331, upload-time = "2026-03-28T17:16:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2", size = 1744414, upload-time = "2026-03-28T17:16:50.968Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21", size = 1719226, upload-time = "2026-03-28T17:16:53.173Z" }, - { url = "https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0", size = 1782110, upload-time = "2026-03-28T17:16:55.362Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e", size = 1884809, upload-time = "2026-03-28T17:16:57.734Z" }, - { url = "https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a", size = 1764938, upload-time = "2026-03-28T17:17:00.221Z" }, - { url = "https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f", size = 1570697, upload-time = "2026-03-28T17:17:02.985Z" }, - { url = "https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c", size = 1702258, upload-time = "2026-03-28T17:17:05.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500", size = 1740287, upload-time = "2026-03-28T17:17:07.712Z" }, - { url = "https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7", size = 1753011, upload-time = "2026-03-28T17:17:10.053Z" }, - { url = "https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073", size = 1566359, upload-time = "2026-03-28T17:17:12.433Z" }, - { url = "https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069", size = 1785537, upload-time = "2026-03-28T17:17:14.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5", size = 1740752, upload-time = "2026-03-28T17:17:17.387Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70", size = 433187, upload-time = "2026-03-28T17:17:19.523Z" }, - { url = "https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3", size = 459778, upload-time = "2026-03-28T17:17:21.964Z" }, - { url = "https://files.pythonhosted.org/packages/6d/29/6657cc37ae04cacc2dbf53fb730a06b6091cc4cbe745028e047c53e6d840/aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57", size = 749363, upload-time = "2026-03-28T17:17:24.044Z" }, - { url = "https://files.pythonhosted.org/packages/90/7f/30ccdf67ca3d24b610067dc63d64dcb91e5d88e27667811640644aa4a85d/aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933", size = 499317, upload-time = "2026-03-28T17:17:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/93/13/e372dd4e68ad04ee25dafb050c7f98b0d91ea643f7352757e87231102555/aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7", size = 500477, upload-time = "2026-03-28T17:17:28.279Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/ee6298e8e586096fb6f5eddd31393d8544f33ae0792c71ecbb4c2bef98ac/aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c", size = 1737227, upload-time = "2026-03-28T17:17:30.587Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b9/a7a0463a09e1a3fe35100f74324f23644bfc3383ac5fd5effe0722a5f0b7/aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349", size = 1694036, upload-time = "2026-03-28T17:17:33.29Z" }, - { url = "https://files.pythonhosted.org/packages/57/7c/8972ae3fb7be00a91aee6b644b2a6a909aedb2c425269a3bfd90115e6f8f/aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329", size = 1786814, upload-time = "2026-03-28T17:17:36.035Z" }, - { url = "https://files.pythonhosted.org/packages/93/01/c81e97e85c774decbaf0d577de7d848934e8166a3a14ad9f8aa5be329d28/aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde", size = 1866676, upload-time = "2026-03-28T17:17:38.441Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5f/5b46fe8694a639ddea2cd035bf5729e4677ea882cb251396637e2ef1590d/aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed", size = 1740842, upload-time = "2026-03-28T17:17:40.783Z" }, - { url = "https://files.pythonhosted.org/packages/20/a2/0d4b03d011cca6b6b0acba8433193c1e484efa8d705ea58295590fe24203/aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f", size = 1566508, upload-time = "2026-03-28T17:17:43.235Z" }, - { url = "https://files.pythonhosted.org/packages/98/17/e689fd500da52488ec5f889effd6404dece6a59de301e380f3c64f167beb/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a", size = 1700569, upload-time = "2026-03-28T17:17:46.165Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0d/66402894dbcf470ef7db99449e436105ea862c24f7ea4c95c683e635af35/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b", size = 1707407, upload-time = "2026-03-28T17:17:48.825Z" }, - { url = "https://files.pythonhosted.org/packages/2f/eb/af0ab1a3650092cbd8e14ef29e4ab0209e1460e1c299996c3f8288b3f1ff/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2", size = 1752214, upload-time = "2026-03-28T17:17:51.206Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bf/72326f8a98e4c666f292f03c385545963cc65e358835d2a7375037a97b57/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e", size = 1562162, upload-time = "2026-03-28T17:17:53.634Z" }, - { url = "https://files.pythonhosted.org/packages/67/9f/13b72435f99151dd9a5469c96b3b5f86aa29b7e785ca7f35cf5e538f74c0/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb", size = 1768904, upload-time = "2026-03-28T17:17:55.991Z" }, - { url = "https://files.pythonhosted.org/packages/18/bc/28d4970e7d5452ac7776cdb5431a1164a0d9cf8bd2fffd67b4fb463aa56d/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165", size = 1723378, upload-time = "2026-03-28T17:17:58.348Z" }, - { url = "https://files.pythonhosted.org/packages/53/74/b32458ca1a7f34d65bdee7aef2036adbe0438123d3d53e2b083c453c24dd/aiohttp-3.13.4-cp314-cp314-win32.whl", hash = "sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9", size = 438711, upload-time = "2026-03-28T17:18:00.728Z" }, - { url = "https://files.pythonhosted.org/packages/40/b2/54b487316c2df3e03a8f3435e9636f8a81a42a69d942164830d193beb56a/aiohttp-3.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8", size = 464977, upload-time = "2026-03-28T17:18:03.367Z" }, - { url = "https://files.pythonhosted.org/packages/47/fb/e41b63c6ce71b07a59243bb8f3b457ee0c3402a619acb9d2c0d21ef0e647/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1", size = 781549, upload-time = "2026-03-28T17:18:05.779Z" }, - { url = "https://files.pythonhosted.org/packages/97/53/532b8d28df1e17e44c4d9a9368b78dcb6bf0b51037522136eced13afa9e8/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c", size = 514383, upload-time = "2026-03-28T17:18:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1f/62e5d400603e8468cd635812d99cb81cfdc08127a3dc474c647615f31339/aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954", size = 518304, upload-time = "2026-03-28T17:18:10.642Z" }, - { url = "https://files.pythonhosted.org/packages/90/57/2326b37b10896447e3c6e0cbef4fe2486d30913639a5cfd1332b5d870f82/aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551", size = 1893433, upload-time = "2026-03-28T17:18:13.121Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b4/a24d82112c304afdb650167ef2fe190957d81cbddac7460bedd245f765aa/aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871", size = 1755901, upload-time = "2026-03-28T17:18:16.21Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2d/0883ef9d878d7846287f036c162a951968f22aabeef3ac97b0bea6f76d5d/aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e", size = 1876093, upload-time = "2026-03-28T17:18:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/ad/52/9204bb59c014869b71971addad6778f005daa72a96eed652c496789d7468/aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8", size = 1970815, upload-time = "2026-03-28T17:18:21.858Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b5/e4eb20275a866dde0f570f411b36c6b48f7b53edfe4f4071aa1b0728098a/aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27", size = 1816223, upload-time = "2026-03-28T17:18:24.729Z" }, - { url = "https://files.pythonhosted.org/packages/d8/23/e98075c5bb146aa61a1239ee1ac7714c85e814838d6cebbe37d3fe19214a/aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7", size = 1649145, upload-time = "2026-03-28T17:18:27.269Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c1/7bad8be33bb06c2bb224b6468874346026092762cbec388c3bdb65a368ee/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb", size = 1816562, upload-time = "2026-03-28T17:18:29.847Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/c00323348695e9a5e316825969c88463dcc24c7e9d443244b8a2c9cf2eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927", size = 1800333, upload-time = "2026-03-28T17:18:32.269Z" }, - { url = "https://files.pythonhosted.org/packages/84/43/9b2147a1df3559f49bd723e22905b46a46c068a53adb54abdca32c4de180/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965", size = 1820617, upload-time = "2026-03-28T17:18:35.238Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7f/b3481a81e7a586d02e99387b18c6dafff41285f6efd3daa2124c01f87eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182", size = 1643417, upload-time = "2026-03-28T17:18:37.949Z" }, - { url = "https://files.pythonhosted.org/packages/8f/72/07181226bc99ce1124e0f89280f5221a82d3ae6a6d9d1973ce429d48e52b/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b", size = 1849286, upload-time = "2026-03-28T17:18:40.534Z" }, - { url = "https://files.pythonhosted.org/packages/1a/e6/1b3566e103eca6da5be4ae6713e112a053725c584e96574caf117568ffef/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba", size = 1782635, upload-time = "2026-03-28T17:18:43.073Z" }, - { url = "https://files.pythonhosted.org/packages/37/58/1b11c71904b8d079eb0c39fe664180dd1e14bebe5608e235d8bfbadc8929/aiohttp-3.13.4-cp314-cp314t-win32.whl", hash = "sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30", size = 472537, upload-time = "2026-03-28T17:18:46.286Z" }, - { url = "https://files.pythonhosted.org/packages/bc/8f/87c56a1a1977d7dddea5b31e12189665a140fdb48a71e9038ff90bb564ec/aiohttp-3.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144", size = 506381, upload-time = "2026-03-28T17:18:48.74Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bd/ede278648914cabbabfdf95e436679b5d4156e417896a9b9f4587169e376/aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360", size = 752158 }, + { url = "https://files.pythonhosted.org/packages/90/de/581c053253c07b480b03785196ca5335e3c606a37dc73e95f6527f1591fe/aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d", size = 501037 }, + { url = "https://files.pythonhosted.org/packages/fa/f9/a5ede193c08f13cc42c0a5b50d1e246ecee9115e4cf6e900d8dbd8fd6acb/aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c", size = 501556 }, + { url = "https://files.pythonhosted.org/packages/d6/10/88ff67cd48a6ec36335b63a640abe86135791544863e0cfe1f065d6cef7a/aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97", size = 1757314 }, + { url = "https://files.pythonhosted.org/packages/8b/15/fdb90a5cf5a1f52845c276e76298c75fbbcc0ac2b4a86551906d54529965/aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576", size = 1731819 }, + { url = "https://files.pythonhosted.org/packages/ec/df/28146785a007f7820416be05d4f28cc207493efd1e8c6c1068e9bdc29198/aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab", size = 1793279 }, + { url = "https://files.pythonhosted.org/packages/10/47/689c743abf62ea7a77774d5722f220e2c912a77d65d368b884d9779ef41b/aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d", size = 1891082 }, + { url = "https://files.pythonhosted.org/packages/b0/b6/f7f4f318c7e58c23b761c9b13b9a3c9b394e0f9d5d76fbc6622fa98509f6/aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e", size = 1773938 }, + { url = "https://files.pythonhosted.org/packages/aa/06/f207cb3121852c989586a6fc16ff854c4fcc8651b86c5d3bd1fc83057650/aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3", size = 1579548 }, + { url = "https://files.pythonhosted.org/packages/6c/58/e1289661a32161e24c1fe479711d783067210d266842523752869cc1d9c2/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83", size = 1714669 }, + { url = "https://files.pythonhosted.org/packages/96/0a/3e86d039438a74a86e6a948a9119b22540bae037d6ba317a042ae3c22711/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763", size = 1754175 }, + { url = "https://files.pythonhosted.org/packages/f4/30/e717fc5df83133ba467a560b6d8ef20197037b4bb5d7075b90037de1018e/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9", size = 1762049 }, + { url = "https://files.pythonhosted.org/packages/e4/28/8f7a2d4492e336e40005151bdd94baf344880a4707573378579f833a64c1/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758", size = 1570861 }, + { url = "https://files.pythonhosted.org/packages/78/45/12e1a3d0645968b1c38de4b23fdf270b8637735ea057d4f84482ff918ad9/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9", size = 1790003 }, + { url = "https://files.pythonhosted.org/packages/eb/0f/60374e18d590de16dcb39d6ff62f39c096c1b958e6f37727b5870026ea30/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d", size = 1737289 }, + { url = "https://files.pythonhosted.org/packages/02/bf/535e58d886cfbc40a8b0013c974afad24ef7632d645bca0b678b70033a60/aiohttp-3.13.4-cp312-cp312-win32.whl", hash = "sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791", size = 434185 }, + { url = "https://files.pythonhosted.org/packages/1e/1a/d92e3325134ebfff6f4069f270d3aac770d63320bd1fcd0eca023e74d9a8/aiohttp-3.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77", size = 461285 }, + { url = "https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538", size = 745744 }, + { url = "https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e", size = 498178 }, + { url = "https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3", size = 498331 }, + { url = "https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2", size = 1744414 }, + { url = "https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21", size = 1719226 }, + { url = "https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0", size = 1782110 }, + { url = "https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e", size = 1884809 }, + { url = "https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a", size = 1764938 }, + { url = "https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f", size = 1570697 }, + { url = "https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c", size = 1702258 }, + { url = "https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500", size = 1740287 }, + { url = "https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7", size = 1753011 }, + { url = "https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073", size = 1566359 }, + { url = "https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069", size = 1785537 }, + { url = "https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5", size = 1740752 }, + { url = "https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70", size = 433187 }, + { url = "https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3", size = 459778 }, + { url = "https://files.pythonhosted.org/packages/6d/29/6657cc37ae04cacc2dbf53fb730a06b6091cc4cbe745028e047c53e6d840/aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57", size = 749363 }, + { url = "https://files.pythonhosted.org/packages/90/7f/30ccdf67ca3d24b610067dc63d64dcb91e5d88e27667811640644aa4a85d/aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933", size = 499317 }, + { url = "https://files.pythonhosted.org/packages/93/13/e372dd4e68ad04ee25dafb050c7f98b0d91ea643f7352757e87231102555/aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7", size = 500477 }, + { url = "https://files.pythonhosted.org/packages/e5/fe/ee6298e8e586096fb6f5eddd31393d8544f33ae0792c71ecbb4c2bef98ac/aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c", size = 1737227 }, + { url = "https://files.pythonhosted.org/packages/b0/b9/a7a0463a09e1a3fe35100f74324f23644bfc3383ac5fd5effe0722a5f0b7/aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349", size = 1694036 }, + { url = "https://files.pythonhosted.org/packages/57/7c/8972ae3fb7be00a91aee6b644b2a6a909aedb2c425269a3bfd90115e6f8f/aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329", size = 1786814 }, + { url = "https://files.pythonhosted.org/packages/93/01/c81e97e85c774decbaf0d577de7d848934e8166a3a14ad9f8aa5be329d28/aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde", size = 1866676 }, + { url = "https://files.pythonhosted.org/packages/5a/5f/5b46fe8694a639ddea2cd035bf5729e4677ea882cb251396637e2ef1590d/aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed", size = 1740842 }, + { url = "https://files.pythonhosted.org/packages/20/a2/0d4b03d011cca6b6b0acba8433193c1e484efa8d705ea58295590fe24203/aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f", size = 1566508 }, + { url = "https://files.pythonhosted.org/packages/98/17/e689fd500da52488ec5f889effd6404dece6a59de301e380f3c64f167beb/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a", size = 1700569 }, + { url = "https://files.pythonhosted.org/packages/d8/0d/66402894dbcf470ef7db99449e436105ea862c24f7ea4c95c683e635af35/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b", size = 1707407 }, + { url = "https://files.pythonhosted.org/packages/2f/eb/af0ab1a3650092cbd8e14ef29e4ab0209e1460e1c299996c3f8288b3f1ff/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2", size = 1752214 }, + { url = "https://files.pythonhosted.org/packages/5a/bf/72326f8a98e4c666f292f03c385545963cc65e358835d2a7375037a97b57/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e", size = 1562162 }, + { url = "https://files.pythonhosted.org/packages/67/9f/13b72435f99151dd9a5469c96b3b5f86aa29b7e785ca7f35cf5e538f74c0/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb", size = 1768904 }, + { url = "https://files.pythonhosted.org/packages/18/bc/28d4970e7d5452ac7776cdb5431a1164a0d9cf8bd2fffd67b4fb463aa56d/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165", size = 1723378 }, + { url = "https://files.pythonhosted.org/packages/53/74/b32458ca1a7f34d65bdee7aef2036adbe0438123d3d53e2b083c453c24dd/aiohttp-3.13.4-cp314-cp314-win32.whl", hash = "sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9", size = 438711 }, + { url = "https://files.pythonhosted.org/packages/40/b2/54b487316c2df3e03a8f3435e9636f8a81a42a69d942164830d193beb56a/aiohttp-3.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8", size = 464977 }, + { url = "https://files.pythonhosted.org/packages/47/fb/e41b63c6ce71b07a59243bb8f3b457ee0c3402a619acb9d2c0d21ef0e647/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1", size = 781549 }, + { url = "https://files.pythonhosted.org/packages/97/53/532b8d28df1e17e44c4d9a9368b78dcb6bf0b51037522136eced13afa9e8/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c", size = 514383 }, + { url = "https://files.pythonhosted.org/packages/1b/1f/62e5d400603e8468cd635812d99cb81cfdc08127a3dc474c647615f31339/aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954", size = 518304 }, + { url = "https://files.pythonhosted.org/packages/90/57/2326b37b10896447e3c6e0cbef4fe2486d30913639a5cfd1332b5d870f82/aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551", size = 1893433 }, + { url = "https://files.pythonhosted.org/packages/d2/b4/a24d82112c304afdb650167ef2fe190957d81cbddac7460bedd245f765aa/aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871", size = 1755901 }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0883ef9d878d7846287f036c162a951968f22aabeef3ac97b0bea6f76d5d/aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e", size = 1876093 }, + { url = "https://files.pythonhosted.org/packages/ad/52/9204bb59c014869b71971addad6778f005daa72a96eed652c496789d7468/aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8", size = 1970815 }, + { url = "https://files.pythonhosted.org/packages/d6/b5/e4eb20275a866dde0f570f411b36c6b48f7b53edfe4f4071aa1b0728098a/aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27", size = 1816223 }, + { url = "https://files.pythonhosted.org/packages/d8/23/e98075c5bb146aa61a1239ee1ac7714c85e814838d6cebbe37d3fe19214a/aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7", size = 1649145 }, + { url = "https://files.pythonhosted.org/packages/d6/c1/7bad8be33bb06c2bb224b6468874346026092762cbec388c3bdb65a368ee/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb", size = 1816562 }, + { url = "https://files.pythonhosted.org/packages/5c/10/c00323348695e9a5e316825969c88463dcc24c7e9d443244b8a2c9cf2eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927", size = 1800333 }, + { url = "https://files.pythonhosted.org/packages/84/43/9b2147a1df3559f49bd723e22905b46a46c068a53adb54abdca32c4de180/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965", size = 1820617 }, + { url = "https://files.pythonhosted.org/packages/a9/7f/b3481a81e7a586d02e99387b18c6dafff41285f6efd3daa2124c01f87eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182", size = 1643417 }, + { url = "https://files.pythonhosted.org/packages/8f/72/07181226bc99ce1124e0f89280f5221a82d3ae6a6d9d1973ce429d48e52b/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b", size = 1849286 }, + { url = "https://files.pythonhosted.org/packages/1a/e6/1b3566e103eca6da5be4ae6713e112a053725c584e96574caf117568ffef/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba", size = 1782635 }, + { url = "https://files.pythonhosted.org/packages/37/58/1b11c71904b8d079eb0c39fe664180dd1e14bebe5608e235d8bfbadc8929/aiohttp-3.13.4-cp314-cp314t-win32.whl", hash = "sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30", size = 472537 }, + { url = "https://files.pythonhosted.org/packages/bc/8f/87c56a1a1977d7dddea5b31e12189665a140fdb48a71e9038ff90bb564ec/aiohttp-3.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144", size = 506381 }, ] [[package]] @@ -152,18 +152,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608, upload-time = "2024-11-06T10:44:54.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981, upload-time = "2024-11-06T10:44:52.917Z" }, + { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981 }, ] [[package]] name = "aiolimiter" version = "1.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/23/b52debf471f7a1e42e362d959a3982bdcb4fe13a5d46e63d28868807a79c/aiolimiter-1.2.1.tar.gz", hash = "sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9", size = 7185, upload-time = "2024-12-08T15:31:51.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/23/b52debf471f7a1e42e362d959a3982bdcb4fe13a5d46e63d28868807a79c/aiolimiter-1.2.1.tar.gz", hash = "sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9", size = 7185 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/ba/df6e8e1045aebc4778d19b8a3a9bc1808adb1619ba94ca354d9ba17d86c3/aiolimiter-1.2.1-py3-none-any.whl", hash = "sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7", size = 6711, upload-time = "2024-12-08T15:31:49.874Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ba/df6e8e1045aebc4778d19b8a3a9bc1808adb1619ba94ca354d9ba17d86c3/aiolimiter-1.2.1-py3-none-any.whl", hash = "sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7", size = 6711 }, ] [[package]] @@ -174,18 +174,18 @@ dependencies = [ { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, ] [[package]] name = "aiosqlite" version = "0.22.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405 }, ] [[package]] @@ -197,9 +197,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893 }, ] [[package]] @@ -209,27 +209,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944 }, ] [[package]] name = "annotated-doc" version = "0.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] @@ -246,16 +246,16 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820, upload-time = "2026-03-18T18:43:08.017Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400, upload-time = "2026-03-18T18:43:06.526Z" }, + { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400 }, ] [[package]] name = "antlr4-python3-runtime" version = "4.9.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } [[package]] name = "anyio" @@ -265,9 +265,9 @@ dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353 }, ] [[package]] @@ -277,9 +277,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657 }, ] [[package]] @@ -289,142 +289,142 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, - { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, - { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, - { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, - { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, - { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, - { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, - { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, - { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, - { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393 }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328 }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269 }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558 }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364 }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637 }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934 }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158 }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597 }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231 }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121 }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177 }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090 }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246 }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126 }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343 }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777 }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180 }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715 }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149 }, ] [[package]] name = "asgiref" version = "3.11.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345 }, ] [[package]] name = "asyncpg" version = "0.31.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, - { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, - { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, - { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" }, - { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" }, - { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" }, - { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" }, - { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" }, - { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" }, - { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" }, - { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" }, - { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" }, - { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" }, - { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" }, - { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" }, - { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" }, - { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042 }, + { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504 }, + { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321 }, + { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685 }, + { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858 }, + { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852 }, + { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175 }, + { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111 }, + { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928 }, + { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067 }, + { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156 }, + { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636 }, + { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079 }, + { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606 }, + { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569 }, + { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867 }, + { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349 }, + { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428 }, + { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678 }, + { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505 }, + { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744 }, + { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251 }, + { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901 }, + { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280 }, + { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931 }, + { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608 }, + { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738 }, + { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026 }, + { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426 }, + { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495 }, + { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062 }, ] [[package]] name = "attrs" version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548 }, ] [[package]] name = "audioop-lts" version = "0.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, - { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, - { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, - { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, - { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, - { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, - { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, - { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, - { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, - { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, - { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, - { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, - { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, - { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, - { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, - { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, - { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, - { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, - { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, - { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, - { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, - { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, - { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, - { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, - { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, - { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523 }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455 }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997 }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844 }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056 }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892 }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660 }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143 }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313 }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044 }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766 }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640 }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052 }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185 }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503 }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173 }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096 }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748 }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329 }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407 }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811 }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470 }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878 }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867 }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001 }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046 }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788 }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472 }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279 }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568 }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942 }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603 }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754 }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332 }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396 }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811 }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483 }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885 }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899 }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046 }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843 }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490 }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297 }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331 }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697 }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206 }, ] [[package]] @@ -434,33 +434,33 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134, upload-time = "2026-03-02T07:44:01.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134 } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197, upload-time = "2026-03-02T07:44:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197 }, ] [[package]] name = "av" version = "17.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/eb/abca886df3a091bc406feb5ff71b4c4f426beaae6b71b9697264ce8c7211/av-17.0.0.tar.gz", hash = "sha256:c53685df73775a8763c375c7b2d62a6cb149d992a26a4b098204da42ade8c3df", size = 4410769, upload-time = "2026-03-14T14:38:45.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/eb/abca886df3a091bc406feb5ff71b4c4f426beaae6b71b9697264ce8c7211/av-17.0.0.tar.gz", hash = "sha256:c53685df73775a8763c375c7b2d62a6cb149d992a26a4b098204da42ade8c3df", size = 4410769 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/fb/55e3b5b5d1fc61466292f26fbcbabafa2642f378dc48875f8f554591e1a4/av-17.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ed4013fac77c309a4a68141dcf6148f1821bb1073a36d4289379762a6372f711", size = 23238424, upload-time = "2026-03-14T14:38:05.856Z" }, - { url = "https://files.pythonhosted.org/packages/52/03/9ace1acc08bc9ae38c14bf3a4b1360e995e4d999d1d33c2cbd7c9e77582a/av-17.0.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:e44b6c83e9f3be9f79ee87d0b77a27cea9a9cd67bd630362c86b7e56a748dfbb", size = 18709043, upload-time = "2026-03-14T14:38:08.288Z" }, - { url = "https://files.pythonhosted.org/packages/00/c0/637721f3cd5bb8bd16105a1a08efd781fc12f449931bdb3a4d0cfd63fa55/av-17.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b440da6ac47da0629d509316f24bcd858f33158dbdd0f1b7293d71e99beb26de", size = 34018780, upload-time = "2026-03-14T14:38:10.45Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/d19bc3257dd985d55337d7f0414c019414b97e16cd3690ebf9941a847543/av-17.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1060cba85f97f4a337311169d92c0b5e143452cfa5ca0e65fa499d7955e8592e", size = 36358757, upload-time = "2026-03-14T14:38:13.092Z" }, - { url = "https://files.pythonhosted.org/packages/52/6c/a1f4f2677bae6f2ade7a8a18e90ebdcf70690c9b1c4e40e118aa30fa313f/av-17.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:deda202e6021cfc7ba3e816897760ec5431309d59a4da1f75df3c0e9413d71e7", size = 35195281, upload-time = "2026-03-14T14:38:15.789Z" }, - { url = "https://files.pythonhosted.org/packages/90/ea/52b0fc6f69432c7bf3f5fbe6f707113650aa40a1a05b9096ffc2bba4f77d/av-17.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ffaf266a1a9c2148072de0a4b5ae98061465178d2cfaa69ee089761149342974", size = 37444817, upload-time = "2026-03-14T14:38:18.563Z" }, - { url = "https://files.pythonhosted.org/packages/34/ad/d2172966282cb8f146c13b6be7416efefde74186460c5e1708ddfc13dba6/av-17.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:45a35a40b2875bf2f98de7c952d74d960f92f319734e6d28e03b4c62a49e6f49", size = 28888553, upload-time = "2026-03-14T14:38:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/b0/bb/c5a4c4172c514d631fb506e6366b503576b8c7f29809cf42aca73e28ff01/av-17.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:3d32e9b5c5bbcb872a0b6917b352a1db8a42142237826c9b49a36d5dbd9e9c26", size = 21916910, upload-time = "2026-03-14T14:38:23.706Z" }, - { url = "https://files.pythonhosted.org/packages/7f/8e/c40ac08e63f79387c59f6ecc38f47d4c942b549130eee579ec1a91f6a291/av-17.0.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:d13250fb4b4522e9a6bec32da082556d5f257110ea223758151375748d9bbe25", size = 23483029, upload-time = "2026-03-14T14:38:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/a9/fb/b4419494bfc249163ec393c613966d66db7e95c76da3345711cd115a79df/av-17.0.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:dbb56aa3b7ae72451d1bf6e9d37c7d83d39b97af712f73583ff419fbf08fc237", size = 18920446, upload-time = "2026-03-14T14:38:27.905Z" }, - { url = "https://files.pythonhosted.org/packages/30/62/c2306d91602ddad2c56106f21dcb334fd51d5ea2e952f7fa025bb8aa39fc/av-17.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a213ac9e83b7ab12c2e9f277a09cac8e9d85cf0883efdab7a87a60e2e4e48879", size = 37477266, upload-time = "2026-03-14T14:38:30.404Z" }, - { url = "https://files.pythonhosted.org/packages/28/cd/c8510a9607886785c0b3ca019d503e888c3757529be42a7287fe2bfa92d5/av-17.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:e15c88bb0921f9435bcc5a27a0863dba571a80ad5e1389c4fcf2073833bb4a74", size = 39572988, upload-time = "2026-03-14T14:38:32.984Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2d/207d9361e25b5abec9be335bbab4df6b6b838e2214be4b374f4cfb285427/av-17.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:096cfd1e9fc896506726c7c42aaf9b370e78c2f257cde4d6ddb6c889bfcc49ec", size = 38399591, upload-time = "2026-03-14T14:38:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/73/ca/307740c6aa2980966bf11383ffcb04bacc5b13f3d268ab4cfb274ad6f793/av-17.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3649ab3d2c7f58049ded1a36e100c0d8fd529cf258f41dd88678ba824034d8c9", size = 40590681, upload-time = "2026-03-14T14:38:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/35/f2/6fdb26d0651adf409864cb2a0d60da107e467d3d1aabc94b234ead54324a/av-17.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e5002271ab2135b551d980c2db8f3299d452e3b9d3633f24f6bb57fffe91cd10", size = 29216337, upload-time = "2026-03-14T14:38:40.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0a/0896b829a39b5669a2d811e1a79598de661693685cd62b31f11d0c18e65b/av-17.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dba98603fc4665b4f750de86fbaf6c0cfaece970671a9b529e0e3d1711e8367e", size = 22071058, upload-time = "2026-03-14T14:38:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fb/55e3b5b5d1fc61466292f26fbcbabafa2642f378dc48875f8f554591e1a4/av-17.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ed4013fac77c309a4a68141dcf6148f1821bb1073a36d4289379762a6372f711", size = 23238424 }, + { url = "https://files.pythonhosted.org/packages/52/03/9ace1acc08bc9ae38c14bf3a4b1360e995e4d999d1d33c2cbd7c9e77582a/av-17.0.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:e44b6c83e9f3be9f79ee87d0b77a27cea9a9cd67bd630362c86b7e56a748dfbb", size = 18709043 }, + { url = "https://files.pythonhosted.org/packages/00/c0/637721f3cd5bb8bd16105a1a08efd781fc12f449931bdb3a4d0cfd63fa55/av-17.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b440da6ac47da0629d509316f24bcd858f33158dbdd0f1b7293d71e99beb26de", size = 34018780 }, + { url = "https://files.pythonhosted.org/packages/d2/59/d19bc3257dd985d55337d7f0414c019414b97e16cd3690ebf9941a847543/av-17.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1060cba85f97f4a337311169d92c0b5e143452cfa5ca0e65fa499d7955e8592e", size = 36358757 }, + { url = "https://files.pythonhosted.org/packages/52/6c/a1f4f2677bae6f2ade7a8a18e90ebdcf70690c9b1c4e40e118aa30fa313f/av-17.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:deda202e6021cfc7ba3e816897760ec5431309d59a4da1f75df3c0e9413d71e7", size = 35195281 }, + { url = "https://files.pythonhosted.org/packages/90/ea/52b0fc6f69432c7bf3f5fbe6f707113650aa40a1a05b9096ffc2bba4f77d/av-17.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ffaf266a1a9c2148072de0a4b5ae98061465178d2cfaa69ee089761149342974", size = 37444817 }, + { url = "https://files.pythonhosted.org/packages/34/ad/d2172966282cb8f146c13b6be7416efefde74186460c5e1708ddfc13dba6/av-17.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:45a35a40b2875bf2f98de7c952d74d960f92f319734e6d28e03b4c62a49e6f49", size = 28888553 }, + { url = "https://files.pythonhosted.org/packages/b0/bb/c5a4c4172c514d631fb506e6366b503576b8c7f29809cf42aca73e28ff01/av-17.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:3d32e9b5c5bbcb872a0b6917b352a1db8a42142237826c9b49a36d5dbd9e9c26", size = 21916910 }, + { url = "https://files.pythonhosted.org/packages/7f/8e/c40ac08e63f79387c59f6ecc38f47d4c942b549130eee579ec1a91f6a291/av-17.0.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:d13250fb4b4522e9a6bec32da082556d5f257110ea223758151375748d9bbe25", size = 23483029 }, + { url = "https://files.pythonhosted.org/packages/a9/fb/b4419494bfc249163ec393c613966d66db7e95c76da3345711cd115a79df/av-17.0.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:dbb56aa3b7ae72451d1bf6e9d37c7d83d39b97af712f73583ff419fbf08fc237", size = 18920446 }, + { url = "https://files.pythonhosted.org/packages/30/62/c2306d91602ddad2c56106f21dcb334fd51d5ea2e952f7fa025bb8aa39fc/av-17.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a213ac9e83b7ab12c2e9f277a09cac8e9d85cf0883efdab7a87a60e2e4e48879", size = 37477266 }, + { url = "https://files.pythonhosted.org/packages/28/cd/c8510a9607886785c0b3ca019d503e888c3757529be42a7287fe2bfa92d5/av-17.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:e15c88bb0921f9435bcc5a27a0863dba571a80ad5e1389c4fcf2073833bb4a74", size = 39572988 }, + { url = "https://files.pythonhosted.org/packages/7d/2d/207d9361e25b5abec9be335bbab4df6b6b838e2214be4b374f4cfb285427/av-17.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:096cfd1e9fc896506726c7c42aaf9b370e78c2f257cde4d6ddb6c889bfcc49ec", size = 38399591 }, + { url = "https://files.pythonhosted.org/packages/73/ca/307740c6aa2980966bf11383ffcb04bacc5b13f3d268ab4cfb274ad6f793/av-17.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3649ab3d2c7f58049ded1a36e100c0d8fd529cf258f41dd88678ba824034d8c9", size = 40590681 }, + { url = "https://files.pythonhosted.org/packages/35/f2/6fdb26d0651adf409864cb2a0d60da107e467d3d1aabc94b234ead54324a/av-17.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e5002271ab2135b551d980c2db8f3299d452e3b9d3633f24f6bb57fffe91cd10", size = 29216337 }, + { url = "https://files.pythonhosted.org/packages/41/0a/0896b829a39b5669a2d811e1a79598de661693685cd62b31f11d0c18e65b/av-17.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dba98603fc4665b4f750de86fbaf6c0cfaece970671a9b529e0e3d1711e8367e", size = 22071058 }, ] [[package]] @@ -472,9 +472,9 @@ dependencies = [ { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940, upload-time = "2025-03-27T02:46:20.606Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005, upload-time = "2025-03-27T02:46:22.356Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005 }, ] [[package]] @@ -485,9 +485,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/83/bbde3faa84ddcb8eb0eca4b3ffb3221252281db4ce351300fe248c5c70b1/azure_core-1.39.0.tar.gz", hash = "sha256:8a90a562998dd44ce84597590fff6249701b98c0e8797c95fcdd695b54c35d74", size = 367531, upload-time = "2026-03-19T01:31:29.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/83/bbde3faa84ddcb8eb0eca4b3ffb3221252281db4ce351300fe248c5c70b1/azure_core-1.39.0.tar.gz", hash = "sha256:8a90a562998dd44ce84597590fff6249701b98c0e8797c95fcdd695b54c35d74", size = 367531 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d6/8ebcd05b01a580f086ac9a97fb9fac65c09a4b012161cc97c21a336e880b/azure_core-1.39.0-py3-none-any.whl", hash = "sha256:4ac7b70fab5438c3f68770649a78daf97833caa83827f91df9c14e0e0ea7d34f", size = 218318, upload-time = "2026-03-19T01:31:31.25Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d6/8ebcd05b01a580f086ac9a97fb9fac65c09a4b012161cc97c21a336e880b/azure_core-1.39.0-py3-none-any.whl", hash = "sha256:4ac7b70fab5438c3f68770649a78daf97833caa83827f91df9c14e0e0ea7d34f", size = 218318 }, ] [[package]] @@ -501,18 +501,18 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/0e/3a63efb48aa4a5ae2cfca61ee152fbcb668092134d3eb8bfda472dd5c617/azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6", size = 286304, upload-time = "2026-03-13T01:12:20.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/0e/3a63efb48aa4a5ae2cfca61ee152fbcb668092134d3eb8bfda472dd5c617/azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6", size = 286304 } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/9a/417b3a533e01953a7c618884df2cb05a71e7b68bdbce4fbdb62349d2a2e8/azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c", size = 192138, upload-time = "2026-03-13T01:12:22.951Z" }, + { url = "https://files.pythonhosted.org/packages/49/9a/417b3a533e01953a7c618884df2cb05a71e7b68bdbce4fbdb62349d2a2e8/azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c", size = 192138 }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554 } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845 }, ] [[package]] @@ -527,75 +527,75 @@ dependencies = [ { name = "platformdirs" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/5d/54c79aaaa9aa1278af24cae98d81d6ef635ad840f046bc2ccb5041ddeb1b/banks-2.4.1.tar.gz", hash = "sha256:8cbf1553f14c44d4f7e9c2064ad9212ce53ee4da000b2f8308d548b60db56655", size = 188033, upload-time = "2026-02-17T11:21:14.855Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/5d/54c79aaaa9aa1278af24cae98d81d6ef635ad840f046bc2ccb5041ddeb1b/banks-2.4.1.tar.gz", hash = "sha256:8cbf1553f14c44d4f7e9c2064ad9212ce53ee4da000b2f8308d548b60db56655", size = 188033 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/5a/f38b49e8b225b0c774e97c9495e52ab9ccdf6d82bde68c513bd736820eb2/banks-2.4.1-py3-none-any.whl", hash = "sha256:40e6d9b6e9b69fb403fa31f2853b3297e4919c1b6f2179b2119d2d4473c6ed13", size = 35032, upload-time = "2026-02-17T11:21:13.236Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5a/f38b49e8b225b0c774e97c9495e52ab9ccdf6d82bde68c513bd736820eb2/banks-2.4.1-py3-none-any.whl", hash = "sha256:40e6d9b6e9b69fb403fa31f2853b3297e4919c1b6f2179b2119d2d4473c6ed13", size = 35032 }, ] [[package]] name = "bcrypt" version = "5.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386 } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" }, - { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" }, - { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" }, - { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" }, - { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" }, - { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" }, - { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" }, - { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" }, - { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, - { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, - { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, - { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, - { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, - { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, - { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, - { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, - { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, - { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, - { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, - { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, - { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, - { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, - { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, - { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, - { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, - { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, - { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, - { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, - { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, - { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, - { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, - { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, + { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806 }, + { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626 }, + { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853 }, + { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793 }, + { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930 }, + { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194 }, + { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381 }, + { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750 }, + { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757 }, + { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740 }, + { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197 }, + { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974 }, + { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498 }, + { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853 }, + { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626 }, + { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862 }, + { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544 }, + { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787 }, + { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753 }, + { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587 }, + { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178 }, + { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295 }, + { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700 }, + { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034 }, + { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766 }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449 }, + { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310 }, + { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761 }, + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553 }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009 }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029 }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907 }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500 }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412 }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486 }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940 }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776 }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922 }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367 }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187 }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752 }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881 }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931 }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313 }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290 }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253 }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084 }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185 }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656 }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662 }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240 }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152 }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284 }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643 }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698 }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725 }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912 }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953 }, ] [[package]] @@ -606,18 +606,18 @@ dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721 }, ] [[package]] name = "billiard" version = "4.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f", size = 156537, upload-time = "2025-11-30T13:28:48.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f", size = 156537 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070, upload-time = "2025-11-30T13:28:47.016Z" }, + { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070 }, ] [[package]] @@ -627,29 +627,29 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663, upload-time = "2025-11-17T12:27:44.482Z" }, - { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939, upload-time = "2025-11-17T12:27:46.19Z" }, - { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835, upload-time = "2025-11-17T12:27:48.167Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550, upload-time = "2025-11-17T12:27:49.958Z" }, - { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686, upload-time = "2025-11-17T12:27:52.062Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939, upload-time = "2025-11-17T12:27:53.847Z" }, - { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759, upload-time = "2025-11-17T12:27:56.176Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322, upload-time = "2025-11-17T12:27:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635, upload-time = "2025-11-17T12:28:00.118Z" }, - { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650, upload-time = "2025-11-17T12:28:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008, upload-time = "2025-11-17T12:28:04.589Z" }, - { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959, upload-time = "2025-11-17T12:28:06.862Z" }, - { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456, upload-time = "2025-11-17T12:28:08.779Z" }, - { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624, upload-time = "2025-11-17T12:28:14.197Z" }, - { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081, upload-time = "2025-11-17T12:28:16.436Z" }, - { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486, upload-time = "2025-11-17T12:28:18.008Z" }, - { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944, upload-time = "2025-11-17T12:28:19.654Z" }, - { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825, upload-time = "2025-11-17T12:28:21.354Z" }, - { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771, upload-time = "2025-11-17T12:28:23.637Z" }, - { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213, upload-time = "2025-11-17T12:28:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695, upload-time = "2025-11-17T12:28:28.401Z" }, + { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663 }, + { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939 }, + { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835 }, + { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550 }, + { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686 }, + { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939 }, + { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759 }, + { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322 }, + { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635 }, + { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650 }, + { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008 }, + { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959 }, + { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456 }, + { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624 }, + { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081 }, + { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486 }, + { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944 }, + { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825 }, + { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771 }, + { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213 }, + { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695 }, ] [[package]] @@ -661,9 +661,9 @@ dependencies = [ { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/f6/b280afd91b2284744c0bb26afa1272bd60270726b4d3999fc27b68200854/boto3-1.42.77.tar.gz", hash = "sha256:c6d9b05e5b86767d4c6ef762f155c891366e5951162f71d030e109fe531f4fd9", size = 112773, upload-time = "2026-03-26T19:25:35.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/f6/b280afd91b2284744c0bb26afa1272bd60270726b4d3999fc27b68200854/boto3-1.42.77.tar.gz", hash = "sha256:c6d9b05e5b86767d4c6ef762f155c891366e5951162f71d030e109fe531f4fd9", size = 112773 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/e6/e27fb0956bd52e6ac222d47eaa04d267887ca1d52a6b3c25d2006c8dc9e6/boto3-1.42.77-py3-none-any.whl", hash = "sha256:95eb3ef693068586f70ca3f29c43701c34a9a73d0df413ea7eaff138efa4a6b9", size = 140555, upload-time = "2026-03-26T19:25:32.069Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e6/e27fb0956bd52e6ac222d47eaa04d267887ca1d52a6b3c25d2006c8dc9e6/boto3-1.42.77-py3-none-any.whl", hash = "sha256:95eb3ef693068586f70ca3f29c43701c34a9a73d0df413ea7eaff138efa4a6b9", size = 140555 }, ] [[package]] @@ -675,18 +675,18 @@ dependencies = [ { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/97/1800633988e890b4eea0706b2671342eddfeb33c1eb1d2fe28a8117f7907/botocore-1.42.77.tar.gz", hash = "sha256:cbb0ac410fab4aa0839a521329f970b271ec298d67465ed7fa7d095c0dad9f48", size = 15023911, upload-time = "2026-03-26T19:25:21.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/97/1800633988e890b4eea0706b2671342eddfeb33c1eb1d2fe28a8117f7907/botocore-1.42.77.tar.gz", hash = "sha256:cbb0ac410fab4aa0839a521329f970b271ec298d67465ed7fa7d095c0dad9f48", size = 15023911 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/7c/6280e6b61f8c232eebd72cae950ed52ce2f38fecf2d178713de3cb1f6298/botocore-1.42.77-py3-none-any.whl", hash = "sha256:807bc2c3825bec6f025506ceeba5f7f111a00de8d58f35c679ee16c8ff6e7b10", size = 14699672, upload-time = "2026-03-26T19:25:15.996Z" }, + { url = "https://files.pythonhosted.org/packages/44/7c/6280e6b61f8c232eebd72cae950ed52ce2f38fecf2d178713de3cb1f6298/botocore-1.42.77-py3-none-any.whl", hash = "sha256:807bc2c3825bec6f025506ceeba5f7f111a00de8d58f35c679ee16c8ff6e7b10", size = 14699672 }, ] [[package]] name = "bracex" version = "2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508 }, ] [[package]] @@ -698,43 +698,43 @@ dependencies = [ { name = "packaging" }, { name = "pyproject-hooks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/1d/ab15c8ac57f4ee8778d7633bc6685f808ab414437b8644f555389cdc875e/build-1.4.2.tar.gz", hash = "sha256:35b14e1ee329c186d3f08466003521ed7685ec15ecffc07e68d706090bf161d1", size = 83433, upload-time = "2026-03-25T14:20:27.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/1d/ab15c8ac57f4ee8778d7633bc6685f808ab414437b8644f555389cdc875e/build-1.4.2.tar.gz", hash = "sha256:35b14e1ee329c186d3f08466003521ed7685ec15ecffc07e68d706090bf161d1", size = 83433 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl", hash = "sha256:7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88", size = 24643, upload-time = "2026-03-25T14:20:26.568Z" }, + { url = "https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl", hash = "sha256:7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88", size = 24643 }, ] [[package]] name = "cachetools" version = "7.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367, upload-time = "2026-03-09T20:51:29.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918, upload-time = "2026-03-09T20:51:27.33Z" }, + { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918 }, ] [[package]] name = "catalogue" version = "2.0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561, upload-time = "2023-09-25T06:29:24.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325, upload-time = "2023-09-25T06:29:23.337Z" }, + { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325 }, ] [[package]] name = "catsu" version = "0.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/54/84b62d54afe2695a9170f6f434dc8912976a57bf6680aa99557eadb3676f/catsu-0.1.8.tar.gz", hash = "sha256:961cbc239e77b32ee08e5498a7ddcb83a3c08b025fb3fc504f97c14569e9aa74", size = 49998, upload-time = "2026-02-20T18:03:38.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/54/84b62d54afe2695a9170f6f434dc8912976a57bf6680aa99557eadb3676f/catsu-0.1.8.tar.gz", hash = "sha256:961cbc239e77b32ee08e5498a7ddcb83a3c08b025fb3fc504f97c14569e9aa74", size = 49998 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/e8/e337d7d95f58bb950e0dd5a969b36b7188fa79e4c4341a3da9e36e3ea1ea/catsu-0.1.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4fa79883486405b81a1eb79081505a69c3c0f4a782b7199724fc2c15eb6b0274", size = 2214667, upload-time = "2026-02-20T18:03:27.553Z" }, - { url = "https://files.pythonhosted.org/packages/f5/dd/057b3a4591d65fc42605ed6a955dd68797dcc8aea0e02f4ca5990e5edc86/catsu-0.1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a27211d2b0a8bc449c7bf8f553c3add438bf9a2e48cc7a5176e39c103fe8369", size = 2149240, upload-time = "2026-02-20T18:03:29.273Z" }, - { url = "https://files.pythonhosted.org/packages/bb/bd/bfc3d49cb8ca68e08521eea0fd61c120e80f8d52b25beeed39d058e383cd/catsu-0.1.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2629b7a0827574426761575fe549754628d023db7be3b9450a6c05f91e6d249f", size = 2401198, upload-time = "2026-02-20T18:03:30.755Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/3376890cf51b4bdd283db654d6d3b2fb98d6d26cc97c2a8af2097be1eb65/catsu-0.1.8-cp312-cp312-win_amd64.whl", hash = "sha256:87f179efa920f10b7d3f8ef4c54d18c53f827d9ee2fc3b4e919b4c1a005d2758", size = 1959392, upload-time = "2026-02-20T18:03:32.298Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a4/3542483167e1a98998a2d5fb39a50978200c703d8f5fc9ac63acb529ee29/catsu-0.1.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6805074bbfb0c9df144834a02af14573ab423d822207d735b297fb5bed74dee4", size = 2214576, upload-time = "2026-02-20T18:03:33.691Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d5/5f8e90bfcd5b5c3b9e9e51fa644856fa89fb5f4e1f2cefd9e87be05d2e2d/catsu-0.1.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1a81d2098e5dfb1e2394c7eeb594fe349451ebe4b51994e54dde5e4f06ecdb9", size = 2148945, upload-time = "2026-02-20T18:03:34.84Z" }, - { url = "https://files.pythonhosted.org/packages/76/72/edb51405083f3c9e87c0c30498b5ade0950eec8968d986c0baf1723ebb02/catsu-0.1.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b84a6412562370ac42270c965ea065d4f8a8f66eb706ac8d4ab5544fbe38fd", size = 2400941, upload-time = "2026-02-20T18:03:36.145Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ce/254323b237e230e5534b3226551172357751c9633438327a939104b56551/catsu-0.1.8-cp313-cp313-win_amd64.whl", hash = "sha256:50ff392f5b9f0ad18fc11496f62079e36bb57270b5bca966feda220744139fa5", size = 1959463, upload-time = "2026-02-20T18:03:37.453Z" }, + { url = "https://files.pythonhosted.org/packages/78/e8/e337d7d95f58bb950e0dd5a969b36b7188fa79e4c4341a3da9e36e3ea1ea/catsu-0.1.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4fa79883486405b81a1eb79081505a69c3c0f4a782b7199724fc2c15eb6b0274", size = 2214667 }, + { url = "https://files.pythonhosted.org/packages/f5/dd/057b3a4591d65fc42605ed6a955dd68797dcc8aea0e02f4ca5990e5edc86/catsu-0.1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a27211d2b0a8bc449c7bf8f553c3add438bf9a2e48cc7a5176e39c103fe8369", size = 2149240 }, + { url = "https://files.pythonhosted.org/packages/bb/bd/bfc3d49cb8ca68e08521eea0fd61c120e80f8d52b25beeed39d058e383cd/catsu-0.1.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2629b7a0827574426761575fe549754628d023db7be3b9450a6c05f91e6d249f", size = 2401198 }, + { url = "https://files.pythonhosted.org/packages/8f/eb/3376890cf51b4bdd283db654d6d3b2fb98d6d26cc97c2a8af2097be1eb65/catsu-0.1.8-cp312-cp312-win_amd64.whl", hash = "sha256:87f179efa920f10b7d3f8ef4c54d18c53f827d9ee2fc3b4e919b4c1a005d2758", size = 1959392 }, + { url = "https://files.pythonhosted.org/packages/5b/a4/3542483167e1a98998a2d5fb39a50978200c703d8f5fc9ac63acb529ee29/catsu-0.1.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6805074bbfb0c9df144834a02af14573ab423d822207d735b297fb5bed74dee4", size = 2214576 }, + { url = "https://files.pythonhosted.org/packages/ba/d5/5f8e90bfcd5b5c3b9e9e51fa644856fa89fb5f4e1f2cefd9e87be05d2e2d/catsu-0.1.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1a81d2098e5dfb1e2394c7eeb594fe349451ebe4b51994e54dde5e4f06ecdb9", size = 2148945 }, + { url = "https://files.pythonhosted.org/packages/76/72/edb51405083f3c9e87c0c30498b5ade0950eec8968d986c0baf1723ebb02/catsu-0.1.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b84a6412562370ac42270c965ea065d4f8a8f66eb706ac8d4ab5544fbe38fd", size = 2400941 }, + { url = "https://files.pythonhosted.org/packages/4b/ce/254323b237e230e5534b3226551172357751c9633438327a939104b56551/catsu-0.1.8-cp313-cp313-win_amd64.whl", hash = "sha256:50ff392f5b9f0ad18fc11496f62079e36bb57270b5bca966feda220744139fa5", size = 1959463 }, ] [[package]] @@ -752,9 +752,9 @@ dependencies = [ { name = "tzlocal" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/b4/a1233943ab5c8ea05fb877a88a0a0622bf47444b99e4991a8045ac37ea1d/celery-5.6.3.tar.gz", hash = "sha256:177006bd2054b882e9f01be59abd8529e88879ef50d7918a7050c5a9f4e12912", size = 1742243, upload-time = "2026-03-26T12:14:51.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/b4/a1233943ab5c8ea05fb877a88a0a0622bf47444b99e4991a8045ac37ea1d/celery-5.6.3.tar.gz", hash = "sha256:177006bd2054b882e9f01be59abd8529e88879ef50d7918a7050c5a9f4e12912", size = 1742243 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/c9/6eccdda96e098f7ae843162db2d3c149c6931a24fda69fe4ab84d0027eb5/celery-5.6.3-py3-none-any.whl", hash = "sha256:0808f42f80909c4d5833202360ffafb2a4f83f4d8e23e1285d926610e9a7afa6", size = 451235, upload-time = "2026-03-26T12:14:49.491Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/6eccdda96e098f7ae843162db2d3c149c6931a24fda69fe4ab84d0027eb5/celery-5.6.3-py3-none-any.whl", hash = "sha256:0808f42f80909c4d5833202360ffafb2a4f83f4d8e23e1285d926610e9a7afa6", size = 451235 }, ] [package.optional-dependencies] @@ -774,18 +774,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/12/c201f07582068141e88f9a523ab02fdc97de58f2f7c0df775c6c52b9d8dd/cerebras_cloud_sdk-1.67.0.tar.gz", hash = "sha256:3aed6f86c6c7a83ee9d4cfb08a2acea089cebf2af5b8aed116ef79995a4f4813", size = 131536, upload-time = "2026-01-29T23:31:27.306Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/12/c201f07582068141e88f9a523ab02fdc97de58f2f7c0df775c6c52b9d8dd/cerebras_cloud_sdk-1.67.0.tar.gz", hash = "sha256:3aed6f86c6c7a83ee9d4cfb08a2acea089cebf2af5b8aed116ef79995a4f4813", size = 131536 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/5e/36a364f3d1bab4073454b75e7c91dc7ec6879b960063d1a9c929f1c7ea71/cerebras_cloud_sdk-1.67.0-py3-none-any.whl", hash = "sha256:658b79ca2e9c16f75cc6b4e5d523ee014c9e54a88bd39f88905c28ecb33daae1", size = 97807, upload-time = "2026-01-29T23:31:25.77Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/36a364f3d1bab4073454b75e7c91dc7ec6879b960063d1a9c929f1c7ea71/cerebras_cloud_sdk-1.67.0-py3-none-any.whl", hash = "sha256:658b79ca2e9c16f75cc6b4e5d523ee014c9e54a88bd39f88905c28ecb33daae1", size = 97807 }, ] [[package]] name = "certifi" version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 }, ] [[package]] @@ -795,127 +795,127 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, ] [[package]] name = "charset-normalizer" version = "3.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, - { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, - { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, - { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, - { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, - { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, - { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, - { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, - { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, - { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, - { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, - { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, - { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, - { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, - { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, - { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, - { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, - { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, - { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, - { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, - { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, - { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, - { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, - { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, - { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, - { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, - { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, - { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, - { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, - { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, - { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, - { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154 }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191 }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674 }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259 }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276 }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161 }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452 }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272 }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622 }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056 }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751 }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563 }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265 }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229 }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277 }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817 }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823 }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527 }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563 }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587 }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724 }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956 }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923 }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366 }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752 }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296 }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956 }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652 }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940 }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101 }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109 }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458 }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277 }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758 }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299 }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811 }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706 }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706 }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497 }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511 }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133 }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035 }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321 }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973 }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610 }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962 }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595 }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828 }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138 }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679 }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475 }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230 }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045 }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658 }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769 }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328 }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302 }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127 }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840 }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890 }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379 }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043 }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523 }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455 }, ] [[package]] @@ -928,9 +928,9 @@ dependencies = [ { name = "tenacity" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/72/fdf8f89ff439f4ec357af0866c819512391936e4e61b6f15635a48434b8a/chonkie-1.6.0.tar.gz", hash = "sha256:14120d80610c1f549027fc7aa9a5ff604a729b545836f6cadd65d5ae83596279", size = 187056, upload-time = "2026-03-11T04:55:07.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/72/fdf8f89ff439f4ec357af0866c819512391936e4e61b6f15635a48434b8a/chonkie-1.6.0.tar.gz", hash = "sha256:14120d80610c1f549027fc7aa9a5ff604a729b545836f6cadd65d5ae83596279", size = 187056 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/c2/7ea7d3409df220dd0e048b1113b44f47eccab9d517b00b037ab0e34c3c7a/chonkie-1.6.0-py3-none-any.whl", hash = "sha256:aa357e02f5cdacac6f8280c5e8651207c866b4137bcf20904db8670ee0808877", size = 232997, upload-time = "2026-03-11T04:55:05.252Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c2/7ea7d3409df220dd0e048b1113b44f47eccab9d517b00b037ab0e34c3c7a/chonkie-1.6.0-py3-none-any.whl", hash = "sha256:aa357e02f5cdacac6f8280c5e8651207c866b4137bcf20904db8670ee0808877", size = 232997 }, ] [package.optional-dependencies] @@ -981,25 +981,25 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/f5/547c836f488dc74116ea42a2b2355365f4829fe6d925564f4db7775e6d34/chonkie_core-0.9.2.tar.gz", hash = "sha256:a34f457016fb4bedf9d0a62e55afc334670d88f8316d50ba9af8df83be78b56a", size = 49480, upload-time = "2026-01-21T09:09:46.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/f5/547c836f488dc74116ea42a2b2355365f4829fe6d925564f4db7775e6d34/chonkie_core-0.9.2.tar.gz", hash = "sha256:a34f457016fb4bedf9d0a62e55afc334670d88f8316d50ba9af8df83be78b56a", size = 49480 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/55/8825b059e70a3c757c90efa319e35312a2650431aef1cec11b476ee8699b/chonkie_core-0.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d6d11337842ca90713d8b48d42ce823bcc82874437d4071a8aced9d47b66ec76", size = 347854, upload-time = "2026-01-21T09:09:17.49Z" }, - { url = "https://files.pythonhosted.org/packages/11/51/abac8676470c7e7a7967964eb9066e2efc346339c338da7190a41f412bba/chonkie_core-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:77fe2d6b9a709245408b53923dd4ebe1b79e09fdcdc5916df9c97e90c8e13eda", size = 333582, upload-time = "2026-01-21T09:09:18.863Z" }, - { url = "https://files.pythonhosted.org/packages/d4/8c/f62d4ff0efbc08d8c281051ce1752cd6bcb6a7f3e816f8b3c143741d1b86/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0151a74791445985f30bc34cbe7d679e9a716d36e9acf67ed5dc3408be6a426", size = 365189, upload-time = "2026-01-21T09:09:19.884Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f2/cae3bf4174e7d2b8f0c9fe76a341bed8dc48e30069683854ca536fbed5bd/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5ba84745a7daa32749fe8cbdf691428c2bd4cef14e6555db4ce382b2edef05", size = 385232, upload-time = "2026-01-21T09:09:21.088Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/18323d5a7fa3638e9c0aaf00cb1fb1b678546466debb3ad57a6adea9d686/chonkie_core-0.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:ee5093fd6a3f78163445bab5907cc6fd883ccea0514f8866abead0f059683d45", size = 222786, upload-time = "2026-01-21T09:09:21.919Z" }, - { url = "https://files.pythonhosted.org/packages/28/ee/f45c8cb237e5a55eac366c9ac7a4a831329f6cf6f33401609063c1ed660d/chonkie_core-0.9.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6a658cd4fc5cb7c12bc6587246eb545f84d6aa25b86001a92fdcb191cea632c8", size = 347713, upload-time = "2026-01-21T09:09:22.809Z" }, - { url = "https://files.pythonhosted.org/packages/f9/31/0049eb4366cef2171404166e8ff1f39ffe350d7d8921247d262dbb3d4d6c/chonkie_core-0.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ebf47e86be6603cbb940529aed6880655ac7f0bac232952565160fdbea5283d0", size = 333290, upload-time = "2026-01-21T09:09:23.786Z" }, - { url = "https://files.pythonhosted.org/packages/03/d9/3a082faa359e3b24826547bdc725dc9af92b4180b262d3ca6872724cbfbb/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64645ff2c299b953b4a1ff951d1492b4a2b461c624b20604ced5612a8622b030", size = 364600, upload-time = "2026-01-21T09:09:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/04/0b/b89aa90c4f44ce4d82effc064031016bb791979cfd6147c155548e706ef7/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:418748eeea1e09c539fd1d7f7f91c1611765c9d82a523e4c4ae0ed9e30a16b2d", size = 384806, upload-time = "2026-01-21T09:09:25.566Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d9/a7f8577b5550a4323aa9eda16336669b8ad6e8a5ea0c176c9baa25738436/chonkie_core-0.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f3718af3037480023423125e3b4a490c8f4cbf6de38d652169a97dd8ba391953", size = 222393, upload-time = "2026-01-21T09:09:26.897Z" }, - { url = "https://files.pythonhosted.org/packages/72/73/cf6a32cfa9238f19a1d539a1d8371b7d90e21e42458a43fbc949c6476871/chonkie_core-0.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66dc990ac58471fbd12845402726ca69b510602abab7c1c3e52cf8e21f9552e3", size = 363201, upload-time = "2026-01-21T09:09:27.764Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1f/56029d9a557e983cf71d22365b4229c4cfaf09401faa6cbc7e912cef2213/chonkie_core-0.9.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:325c0c853268fbdc37f4a65c3cde68fa56e3a25d164eee9512ceb41edc819902", size = 347292, upload-time = "2026-01-21T09:09:28.676Z" }, - { url = "https://files.pythonhosted.org/packages/21/5b/08b8230d9264007cc7920cf1b1576f2ee1a1ef20d3cd5f8adb5e043e0908/chonkie_core-0.9.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:be9ea3bee05564a721f5c6c8699e1ad5996cf353b2faea2217a08ddee29e2de7", size = 332693, upload-time = "2026-01-21T09:09:29.541Z" }, - { url = "https://files.pythonhosted.org/packages/eb/72/255c918da43a96c90b2bb96f1951a6ea1c513c18b36caecb6e9192275b83/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926db6c26e78b2d84dfb8422073a3f0f20478160ab48204f306fa35f3e1e95d7", size = 364578, upload-time = "2026-01-21T09:09:30.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/80/1710844b9706cd44324446eb368e813ebb4a085e96f469f54b61ddff67dc/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861f147e5932e659dcbe770ca0779a470acc62e9242bff87f36d03eae29644d", size = 384173, upload-time = "2026-01-21T09:09:32.015Z" }, - { url = "https://files.pythonhosted.org/packages/5c/5c/a31b259ea94620ea4e4100ed4cc952ad770f2c7af36293bbc9154efb5c9e/chonkie_core-0.9.2-cp314-cp314-win_amd64.whl", hash = "sha256:83473c708a23652d6dc70142b2e586f965af3031b1d2a5c6336f1fc78614b452", size = 222275, upload-time = "2026-01-21T09:09:34.148Z" }, - { url = "https://files.pythonhosted.org/packages/48/8a/c15c88f59bc9cf6f7ac994689d048fd60fcb72247f6b67ca31dc4eadf2f8/chonkie_core-0.9.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d6c0b89ee5e21d255bcf7d11f17b07ef811a71292717f50889244474cfab8bc", size = 363813, upload-time = "2026-01-21T09:09:35.175Z" }, + { url = "https://files.pythonhosted.org/packages/52/55/8825b059e70a3c757c90efa319e35312a2650431aef1cec11b476ee8699b/chonkie_core-0.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d6d11337842ca90713d8b48d42ce823bcc82874437d4071a8aced9d47b66ec76", size = 347854 }, + { url = "https://files.pythonhosted.org/packages/11/51/abac8676470c7e7a7967964eb9066e2efc346339c338da7190a41f412bba/chonkie_core-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:77fe2d6b9a709245408b53923dd4ebe1b79e09fdcdc5916df9c97e90c8e13eda", size = 333582 }, + { url = "https://files.pythonhosted.org/packages/d4/8c/f62d4ff0efbc08d8c281051ce1752cd6bcb6a7f3e816f8b3c143741d1b86/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0151a74791445985f30bc34cbe7d679e9a716d36e9acf67ed5dc3408be6a426", size = 365189 }, + { url = "https://files.pythonhosted.org/packages/7a/f2/cae3bf4174e7d2b8f0c9fe76a341bed8dc48e30069683854ca536fbed5bd/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5ba84745a7daa32749fe8cbdf691428c2bd4cef14e6555db4ce382b2edef05", size = 385232 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/18323d5a7fa3638e9c0aaf00cb1fb1b678546466debb3ad57a6adea9d686/chonkie_core-0.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:ee5093fd6a3f78163445bab5907cc6fd883ccea0514f8866abead0f059683d45", size = 222786 }, + { url = "https://files.pythonhosted.org/packages/28/ee/f45c8cb237e5a55eac366c9ac7a4a831329f6cf6f33401609063c1ed660d/chonkie_core-0.9.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6a658cd4fc5cb7c12bc6587246eb545f84d6aa25b86001a92fdcb191cea632c8", size = 347713 }, + { url = "https://files.pythonhosted.org/packages/f9/31/0049eb4366cef2171404166e8ff1f39ffe350d7d8921247d262dbb3d4d6c/chonkie_core-0.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ebf47e86be6603cbb940529aed6880655ac7f0bac232952565160fdbea5283d0", size = 333290 }, + { url = "https://files.pythonhosted.org/packages/03/d9/3a082faa359e3b24826547bdc725dc9af92b4180b262d3ca6872724cbfbb/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64645ff2c299b953b4a1ff951d1492b4a2b461c624b20604ced5612a8622b030", size = 364600 }, + { url = "https://files.pythonhosted.org/packages/04/0b/b89aa90c4f44ce4d82effc064031016bb791979cfd6147c155548e706ef7/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:418748eeea1e09c539fd1d7f7f91c1611765c9d82a523e4c4ae0ed9e30a16b2d", size = 384806 }, + { url = "https://files.pythonhosted.org/packages/3d/d9/a7f8577b5550a4323aa9eda16336669b8ad6e8a5ea0c176c9baa25738436/chonkie_core-0.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f3718af3037480023423125e3b4a490c8f4cbf6de38d652169a97dd8ba391953", size = 222393 }, + { url = "https://files.pythonhosted.org/packages/72/73/cf6a32cfa9238f19a1d539a1d8371b7d90e21e42458a43fbc949c6476871/chonkie_core-0.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66dc990ac58471fbd12845402726ca69b510602abab7c1c3e52cf8e21f9552e3", size = 363201 }, + { url = "https://files.pythonhosted.org/packages/1f/1f/56029d9a557e983cf71d22365b4229c4cfaf09401faa6cbc7e912cef2213/chonkie_core-0.9.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:325c0c853268fbdc37f4a65c3cde68fa56e3a25d164eee9512ceb41edc819902", size = 347292 }, + { url = "https://files.pythonhosted.org/packages/21/5b/08b8230d9264007cc7920cf1b1576f2ee1a1ef20d3cd5f8adb5e043e0908/chonkie_core-0.9.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:be9ea3bee05564a721f5c6c8699e1ad5996cf353b2faea2217a08ddee29e2de7", size = 332693 }, + { url = "https://files.pythonhosted.org/packages/eb/72/255c918da43a96c90b2bb96f1951a6ea1c513c18b36caecb6e9192275b83/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926db6c26e78b2d84dfb8422073a3f0f20478160ab48204f306fa35f3e1e95d7", size = 364578 }, + { url = "https://files.pythonhosted.org/packages/60/80/1710844b9706cd44324446eb368e813ebb4a085e96f469f54b61ddff67dc/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861f147e5932e659dcbe770ca0779a470acc62e9242bff87f36d03eae29644d", size = 384173 }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a31b259ea94620ea4e4100ed4cc952ad770f2c7af36293bbc9154efb5c9e/chonkie_core-0.9.2-cp314-cp314-win_amd64.whl", hash = "sha256:83473c708a23652d6dc70142b2e586f965af3031b1d2a5c6336f1fc78614b452", size = 222275 }, + { url = "https://files.pythonhosted.org/packages/48/8a/c15c88f59bc9cf6f7ac994689d048fd60fcb72247f6b67ca31dc4eadf2f8/chonkie_core-0.9.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d6c0b89ee5e21d255bcf7d11f17b07ef811a71292717f50889244474cfab8bc", size = 363813 }, ] [[package]] @@ -1035,13 +1035,13 @@ dependencies = [ { name = "typing-extensions" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/6d/ab03e16be3ec663e353166f38be082efb51c0988687f8c8eee1416a7e732/chromadb-1.5.5.tar.gz", hash = "sha256:8d669285b77cc288db27583a57b2f85ba451a9b8e3bef85a260cd78e6b57be35", size = 2411397, upload-time = "2026-03-10T09:30:01.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/6d/ab03e16be3ec663e353166f38be082efb51c0988687f8c8eee1416a7e732/chromadb-1.5.5.tar.gz", hash = "sha256:8d669285b77cc288db27583a57b2f85ba451a9b8e3bef85a260cd78e6b57be35", size = 2411397 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/62/ee578f8ccd62928257558b13a3e7c236e402cfb319c9b201b6a75897d644/chromadb-1.5.5-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d590998ed81164afbfb1734bb534b25ec2c9810fc1c5ce53bf8f7ac644a79887", size = 20800888, upload-time = "2026-03-10T09:29:59.546Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ce/430a87d906f79cdc7e23efcd89dd237e3dbedaf6704b40ce1da127993bf8/chromadb-1.5.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5ff2912d20a82fdbf4e27ff3e1c91dab25e2ba2c629f9739bc12c11a3151aac7", size = 20091810, upload-time = "2026-03-10T09:29:56.044Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5a/11543a76ab25c55bec6133bb98ce0dc0f4850acb36600344d8286734a051/chromadb-1.5.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f54e7736ae0eeec436a1c1fb04b77b2c6c4108996790ef16f88327e38ad13cd", size = 20740649, upload-time = "2026-03-10T09:29:49.346Z" }, - { url = "https://files.pythonhosted.org/packages/d3/66/e0b35c41be7c02d6fa37f6c8f61a16b7b20607ddc847574e9a5503fe853b/chromadb-1.5.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb238ae508a6ce68fdd7875e040d7e5aa29d6e40fb651b51f5537b7cda789762", size = 21589423, upload-time = "2026-03-10T09:29:52.724Z" }, - { url = "https://files.pythonhosted.org/packages/a2/df/ce1ffcc0ad3eef8bd35b920809b990e6925ba94b2580dc5bd7ccde0fc06a/chromadb-1.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:3953403b63bb1c05405d10db36d183c4d19a027938c15898510d11943499046f", size = 21915873, upload-time = "2026-03-10T09:30:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/ee578f8ccd62928257558b13a3e7c236e402cfb319c9b201b6a75897d644/chromadb-1.5.5-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d590998ed81164afbfb1734bb534b25ec2c9810fc1c5ce53bf8f7ac644a79887", size = 20800888 }, + { url = "https://files.pythonhosted.org/packages/f8/ce/430a87d906f79cdc7e23efcd89dd237e3dbedaf6704b40ce1da127993bf8/chromadb-1.5.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5ff2912d20a82fdbf4e27ff3e1c91dab25e2ba2c629f9739bc12c11a3151aac7", size = 20091810 }, + { url = "https://files.pythonhosted.org/packages/a8/5a/11543a76ab25c55bec6133bb98ce0dc0f4850acb36600344d8286734a051/chromadb-1.5.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f54e7736ae0eeec436a1c1fb04b77b2c6c4108996790ef16f88327e38ad13cd", size = 20740649 }, + { url = "https://files.pythonhosted.org/packages/d3/66/e0b35c41be7c02d6fa37f6c8f61a16b7b20607ddc847574e9a5503fe853b/chromadb-1.5.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb238ae508a6ce68fdd7875e040d7e5aa29d6e40fb651b51f5537b7cda789762", size = 21589423 }, + { url = "https://files.pythonhosted.org/packages/a2/df/ce1ffcc0ad3eef8bd35b920809b990e6925ba94b2580dc5bd7ccde0fc06a/chromadb-1.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:3953403b63bb1c05405d10db36d183c4d19a027938c15898510d11943499046f", size = 21915873 }, ] [[package]] @@ -1051,9 +1051,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, ] [[package]] @@ -1063,9 +1063,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631 }, ] [[package]] @@ -1075,9 +1075,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051 }, ] [[package]] @@ -1088,18 +1088,18 @@ dependencies = [ { name = "click" }, { name = "prompt-toolkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, ] [[package]] name = "cloudpathlib" version = "0.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126, upload-time = "2025-10-07T22:47:56.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755, upload-time = "2025-10-07T22:47:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755 }, ] [[package]] @@ -1116,18 +1116,18 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/75/4c346f6e2322e545f8452692304bd4eca15a2a0209ab9af6a0d1a7810b67/cohere-5.21.1.tar.gz", hash = "sha256:e5ade4423b928b01ff2038980e1b62b2a5bb412c8ab83e30882753b810a5509f", size = 191272, upload-time = "2026-03-26T15:09:27.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/75/4c346f6e2322e545f8452692304bd4eca15a2a0209ab9af6a0d1a7810b67/cohere-5.21.1.tar.gz", hash = "sha256:e5ade4423b928b01ff2038980e1b62b2a5bb412c8ab83e30882753b810a5509f", size = 191272 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/50/5538f02ec6d10fbb84f29c1b18c68ff2a03d7877926a80275efdf8755a9f/cohere-5.21.1-py3-none-any.whl", hash = "sha256:f15592ec60d8cf12f01563db94ec28c388c61269d9617f23c2d6d910e505344e", size = 334262, upload-time = "2026-03-26T15:09:26.284Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/5538f02ec6d10fbb84f29c1b18c68ff2a03d7877926a80275efdf8755a9f/cohere-5.21.1-py3-none-any.whl", hash = "sha256:f15592ec60d8cf12f01563db94ec28c388c61269d9617f23c2d6d910e505344e", size = 334262 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] @@ -1137,9 +1137,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743 }, ] [[package]] @@ -1154,9 +1154,9 @@ dependencies = [ { name = "pysher" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/77/5e8557041d09b29a960208c560e82c5a79d606396192c9a99b02f79b61dd/composio-0.11.4.tar.gz", hash = "sha256:cb0622fa31926d9ce4f09e4aa7605a7873b4e3e61c7d7d094682025f34a19ebb", size = 170542, upload-time = "2026-03-25T21:47:16.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/77/5e8557041d09b29a960208c560e82c5a79d606396192c9a99b02f79b61dd/composio-0.11.4.tar.gz", hash = "sha256:cb0622fa31926d9ce4f09e4aa7605a7873b4e3e61c7d7d094682025f34a19ebb", size = 170542 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/46/ccc66eb27e753db878dcaea5f001543863f8563b7a0fe754da0964de9cff/composio-0.11.4-py3-none-any.whl", hash = "sha256:7362f3a3ef4c71a37bc5e3983daa12531bbbe36d961ee00c3428a572a15b7d00", size = 116357, upload-time = "2026-03-25T21:47:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/41/46/ccc66eb27e753db878dcaea5f001543863f8563b7a0fe754da0964de9cff/composio-0.11.4-py3-none-any.whl", hash = "sha256:7362f3a3ef4c71a37bc5e3983daa12531bbbe36d961ee00c3428a572a15b7d00", size = 116357 }, ] [[package]] @@ -1171,18 +1171,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/21/f11039315ce859b723f22b5a16bbb9ba53c83568b56621de7136031a5d3c/composio_client-1.29.0.tar.gz", hash = "sha256:5bbc23a47538e9314bb88cdb9144fd270d5a128ce264936a51ab7db51d75801b", size = 220489, upload-time = "2026-03-20T17:39:24.288Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/21/f11039315ce859b723f22b5a16bbb9ba53c83568b56621de7136031a5d3c/composio_client-1.29.0.tar.gz", hash = "sha256:5bbc23a47538e9314bb88cdb9144fd270d5a128ce264936a51ab7db51d75801b", size = 220489 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/85/66e103d68a20fc602e144fab77746521c9a98f9206dee8984ce69feea61e/composio_client-1.29.0-py3-none-any.whl", hash = "sha256:9910268e77eead235e2b08fc504f2cfd11ad4987d756e260a8ba95f45cbf1b0a", size = 248522, upload-time = "2026-03-20T17:39:23.154Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/66e103d68a20fc602e144fab77746521c9a98f9206dee8984ce69feea61e/composio_client-1.29.0-py3-none-any.whl", hash = "sha256:9910268e77eead235e2b08fc504f2cfd11ad4987d756e260a8ba95f45cbf1b0a", size = 248522 }, ] [[package]] name = "confection" version = "1.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/65/efd0fe8a936fc8ca2978cb7b82581fb20d901c6039e746a808f746b7647b/confection-1.3.3.tar.gz", hash = "sha256:f0f6810d567ff73993fe74d218ca5e1ffb6a44fb03f391257fc5d033546cbfaa", size = 54895, upload-time = "2026-03-24T18:45:24.331Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/65/efd0fe8a936fc8ca2978cb7b82581fb20d901c6039e746a808f746b7647b/confection-1.3.3.tar.gz", hash = "sha256:f0f6810d567ff73993fe74d218ca5e1ffb6a44fb03f391257fc5d033546cbfaa", size = 54895 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/e4/d66708bdf0d92fb4d49b22cdff4b10cec38aca5dcd7e81d909bb55c65cd7/confection-1.3.3-py3-none-any.whl", hash = "sha256:b9fef9ee84b237ef4611ec3eb5797b70e13063e6310ad9f15536373f5e313c82", size = 35902, upload-time = "2026-03-24T18:45:22.664Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e4/d66708bdf0d92fb4d49b22cdff4b10cec38aca5dcd7e81d909bb55c65cd7/confection-1.3.3-py3-none-any.whl", hash = "sha256:b9fef9ee84b237ef4611ec3eb5797b70e13063e6310ad9f15536373f5e313c82", size = 35902 }, ] [[package]] @@ -1192,63 +1192,63 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257 }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034 }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672 }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234 }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169 }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859 }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062 }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932 }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730 }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897 }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751 }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486 }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106 }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548 }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297 }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023 }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189 }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251 }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810 }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871 }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264 }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819 }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650 }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769 }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892 }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748 }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554 }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118 }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555 }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027 }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 }, ] [[package]] @@ -1260,9 +1260,9 @@ dependencies = [ { name = "tld" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/54/6d6ceeff4bed42e7a10d6064d35ee43a810e7b3e8beb4abeae8cff4713ae/courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190", size = 206382, upload-time = "2024-10-29T16:40:20.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/54/6d6ceeff4bed42e7a10d6064d35ee43a810e7b3e8beb4abeae8cff4713ae/courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190", size = 206382 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848, upload-time = "2024-10-29T16:40:18.325Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848 }, ] [[package]] @@ -1272,9 +1272,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422 }, ] [[package]] @@ -1284,50 +1284,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, - { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, - { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, - { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, - { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" }, - { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" }, - { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" }, - { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" }, - { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" }, - { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" }, - { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" }, - { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, - { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, - { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, - { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, - { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, - { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, - { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, - { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, - { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401 }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275 }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320 }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082 }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514 }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766 }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535 }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618 }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802 }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425 }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530 }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896 }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348 }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896 }, + { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147 }, + { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221 }, + { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952 }, + { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141 }, + { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178 }, + { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812 }, + { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923 }, + { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695 }, + { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785 }, + { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404 }, + { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549 }, + { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874 }, + { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511 }, + { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692 }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776 }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529 }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827 }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265 }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800 }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771 }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333 }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069 }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358 }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061 }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103 }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255 }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660 }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160 }, ] [[package]] @@ -1341,9 +1341,9 @@ dependencies = [ { name = "rfc3986" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/a6/a8436f8f7b5578461a4e5c0dbc8341fe2596b703704cf0f5acd35953cc85/csvw-1.11.0.tar.gz", hash = "sha256:c156466fab3331861e0cf3cbe0c4538705800bfac98819149cd70ecbe6f152eb", size = 34812, upload-time = "2021-05-06T08:15:15.351Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a6/a8436f8f7b5578461a4e5c0dbc8341fe2596b703704cf0f5acd35953cc85/csvw-1.11.0.tar.gz", hash = "sha256:c156466fab3331861e0cf3cbe0c4538705800bfac98819149cd70ecbe6f152eb", size = 34812 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/ae/afb43a6b88c4202d29e4ec7aca76633d8c530140f4f5a32ee762d07c4607/csvw-1.11.0-py2.py3-none-any.whl", hash = "sha256:243825391308f2568593415364868dda5e50f608fc2bb307fbd79d534af52fd5", size = 35198, upload-time = "2021-05-06T08:15:19.729Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/afb43a6b88c4202d29e4ec7aca76633d8c530140f4f5a32ee762d07c4607/csvw-1.11.0-py2.py3-none-any.whl", hash = "sha256:243825391308f2568593415364868dda5e50f608fc2bb307fbd79d534af52fd5", size = 35198 }, ] [[package]] @@ -1356,26 +1356,26 @@ dependencies = [ { name = "setuptools" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/0f/581de94b64c5f2327a736270bc7e7a5f8fe5cf1ed56a2203b52de4d8986a/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c0cbd46a23b8dc37ccdbd9b447cb5f7fadc361c90e9df17d82ca84b1f019986", size = 1257089, upload-time = "2026-02-04T06:11:32.442Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e9/d55b0e436362f9fe26bd98fefd2dd5d81926121f1d7f799c805e6035bb26/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:5b141ddad1da5f84cf3c2a569a56227a37de649a555d376cbd9b80e8f0373dd8", size = 11918502, upload-time = "2026-02-04T06:11:33.986Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ce/9f29f0b0bb4280c2ebafb3ddb6cdff8ef1c2e185ee020c0ec0ecba7dc934/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d00a62544db4a3caaa58a3c50d39b25613c042b430053ae32384d94eb1d40990", size = 16859601, upload-time = "2026-02-04T06:11:36.227Z" }, - { url = "https://files.pythonhosted.org/packages/b3/86/428d270fd72117d19fb48ed3211aa8a3c8bd7577373252962cb634e0fd01/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:722b93a89647974cbd182b4c7f87fefc7794fff7fc9cbd0303b6447905cc157e", size = 38995338, upload-time = "2026-02-04T06:11:42.789Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f4/d23dbfb9c62cb642c114a30f05d753ba61d6ffbfd8a3a4012fe85a073bcb/ctranslate2-4.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f734dc3757118094663bdaaf713f5090c55c1927fb330a76bb8b84173940e8", size = 18844949, upload-time = "2026-02-04T06:11:45.436Z" }, - { url = "https://files.pythonhosted.org/packages/34/6d/eb49ba05db286b4ea9d5d3fcf5f5cd0a9a5e218d46349618d5041001e303/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b2abf2929756e3ec6246057b56df379995661560a2d776af05f9d97f63afcf5", size = 1256960, upload-time = "2026-02-04T06:11:47.487Z" }, - { url = "https://files.pythonhosted.org/packages/45/5a/b9cce7b00d89fc6fdeaf27587aa52d0597b465058563e93ff50910553bdd/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:857ef3959d6b1c40dc227c715a36db33db2d097164996d6c75b6db8e30828f52", size = 11918645, upload-time = "2026-02-04T06:11:49.599Z" }, - { url = "https://files.pythonhosted.org/packages/ea/03/c0db0a5276599fb44ceafa2f2cb1afd5628808ec406fe036060a39693680/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:393a9e7e989034660526a2c0e8bb65d1924f43d9a5c77d336494a353d16ba2a4", size = 16860452, upload-time = "2026-02-04T06:11:52.276Z" }, - { url = "https://files.pythonhosted.org/packages/0b/03/4e3728ce29d192ee75ed9a2d8589bf4f19edafe5bed3845187de51b179a3/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a3d0682f2b9082e31c73d75b45f16cde77355ab76d7e8356a24c3cb2480a6d3", size = 38995174, upload-time = "2026-02-04T06:11:55.477Z" }, - { url = "https://files.pythonhosted.org/packages/9b/15/6e8e87c6a201d69803a79ac2e29623ce7c2cc9cd1df9db99810cca714373/ctranslate2-4.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:baa6d2b10f57933d8c11791e8522659217918722d07bbef2389a443801125fe7", size = 18844953, upload-time = "2026-02-04T06:11:58.519Z" }, - { url = "https://files.pythonhosted.org/packages/fd/73/8a6b7ba18cad0c8667ee221ddab8c361cb70926440e5b8dd0e81924c28ac/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d5dfb076566551f4959dfd0706f94c923c1931def9b7bb249a2caa6ab23353a0", size = 1257560, upload-time = "2026-02-04T06:12:00.926Z" }, - { url = "https://files.pythonhosted.org/packages/70/c2/8817ca5d6c1b175b23a12f7c8b91484652f8718a76353317e5919b038733/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:eecdb4ed934b384f16e8c01b185b082d6b5ffc7dcbb0b6a6eb48cd465282d957", size = 11918995, upload-time = "2026-02-04T06:12:02.875Z" }, - { url = "https://files.pythonhosted.org/packages/ac/33/b8eb3acc67bbca4d9872fc9ff94db78e6167a7ba5cd932f585d1560effc7/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1aa6796edcc3c8d163c9e39c429d50076d266d68980fed9d1b2443f617c67e9e", size = 16844162, upload-time = "2026-02-04T06:12:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/80/11/6474893b07121057035069a0a483fe1cd8c47878213f282afb4c0c6fc275/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24c0482c51726430fb83724451921c0e539d769c8618dcfd46b1645e7f75960d", size = 38966728, upload-time = "2026-02-04T06:12:07.923Z" }, - { url = "https://files.pythonhosted.org/packages/94/88/8fc7ff435c5e783e5fad9586d839d463e023988dbbbad949d442092d01f1/ctranslate2-4.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:76db234c0446a23d20dd8eeaa7a789cc87d1d05283f48bf3152bae9fa0a69844", size = 19100788, upload-time = "2026-02-04T06:12:10.592Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b3/f100013a76a98d64e67c721bd4559ea4eeb54be3e4ac45f4d801769899af/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:058c9db2277dc8b19ecc86c7937628f69022f341844b9081d2ab642965d88fc6", size = 1280179, upload-time = "2026-02-04T06:12:12.596Z" }, - { url = "https://files.pythonhosted.org/packages/39/22/b77f748015667a5e2ca54a5ee080d7016fce34314f0e8cf904784549305a/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:5abcf885062c7f28a3f9a46be8d185795e8706ac6230ad086cae0bc82917df31", size = 11940166, upload-time = "2026-02-04T06:12:14.054Z" }, - { url = "https://files.pythonhosted.org/packages/7d/78/6d7fd52f646c6ba3343f71277a9bbef33734632949d1651231948b0f0359/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9950acb04a002d5c60ae90a1ddceead1a803af1f00cadd9b1a1dc76e1f017481", size = 16849483, upload-time = "2026-02-04T06:12:17.082Z" }, - { url = "https://files.pythonhosted.org/packages/40/27/58769ff15ac31b44205bd7a8aeca80cf7357c657ea5df1b94ce0f5c83771/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1dcc734e92e3f1ceeaa0c42bbfd009352857be179ecd4a7ed6cccc086a202f58", size = 38949393, upload-time = "2026-02-04T06:12:21.302Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5c/9fa0ad6462b62efd0fb5ac1100eee47bc96ecc198ff4e237c731e5473616/ctranslate2-4.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dfb7657bdb7b8211c8f9ecb6f3b70bc0db0e0384d01a8b1808cb66fe7199df59", size = 19123451, upload-time = "2026-02-04T06:12:24.115Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0f/581de94b64c5f2327a736270bc7e7a5f8fe5cf1ed56a2203b52de4d8986a/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c0cbd46a23b8dc37ccdbd9b447cb5f7fadc361c90e9df17d82ca84b1f019986", size = 1257089 }, + { url = "https://files.pythonhosted.org/packages/3d/e9/d55b0e436362f9fe26bd98fefd2dd5d81926121f1d7f799c805e6035bb26/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:5b141ddad1da5f84cf3c2a569a56227a37de649a555d376cbd9b80e8f0373dd8", size = 11918502 }, + { url = "https://files.pythonhosted.org/packages/ec/ce/9f29f0b0bb4280c2ebafb3ddb6cdff8ef1c2e185ee020c0ec0ecba7dc934/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d00a62544db4a3caaa58a3c50d39b25613c042b430053ae32384d94eb1d40990", size = 16859601 }, + { url = "https://files.pythonhosted.org/packages/b3/86/428d270fd72117d19fb48ed3211aa8a3c8bd7577373252962cb634e0fd01/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:722b93a89647974cbd182b4c7f87fefc7794fff7fc9cbd0303b6447905cc157e", size = 38995338 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/d23dbfb9c62cb642c114a30f05d753ba61d6ffbfd8a3a4012fe85a073bcb/ctranslate2-4.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f734dc3757118094663bdaaf713f5090c55c1927fb330a76bb8b84173940e8", size = 18844949 }, + { url = "https://files.pythonhosted.org/packages/34/6d/eb49ba05db286b4ea9d5d3fcf5f5cd0a9a5e218d46349618d5041001e303/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b2abf2929756e3ec6246057b56df379995661560a2d776af05f9d97f63afcf5", size = 1256960 }, + { url = "https://files.pythonhosted.org/packages/45/5a/b9cce7b00d89fc6fdeaf27587aa52d0597b465058563e93ff50910553bdd/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:857ef3959d6b1c40dc227c715a36db33db2d097164996d6c75b6db8e30828f52", size = 11918645 }, + { url = "https://files.pythonhosted.org/packages/ea/03/c0db0a5276599fb44ceafa2f2cb1afd5628808ec406fe036060a39693680/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:393a9e7e989034660526a2c0e8bb65d1924f43d9a5c77d336494a353d16ba2a4", size = 16860452 }, + { url = "https://files.pythonhosted.org/packages/0b/03/4e3728ce29d192ee75ed9a2d8589bf4f19edafe5bed3845187de51b179a3/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a3d0682f2b9082e31c73d75b45f16cde77355ab76d7e8356a24c3cb2480a6d3", size = 38995174 }, + { url = "https://files.pythonhosted.org/packages/9b/15/6e8e87c6a201d69803a79ac2e29623ce7c2cc9cd1df9db99810cca714373/ctranslate2-4.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:baa6d2b10f57933d8c11791e8522659217918722d07bbef2389a443801125fe7", size = 18844953 }, + { url = "https://files.pythonhosted.org/packages/fd/73/8a6b7ba18cad0c8667ee221ddab8c361cb70926440e5b8dd0e81924c28ac/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d5dfb076566551f4959dfd0706f94c923c1931def9b7bb249a2caa6ab23353a0", size = 1257560 }, + { url = "https://files.pythonhosted.org/packages/70/c2/8817ca5d6c1b175b23a12f7c8b91484652f8718a76353317e5919b038733/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:eecdb4ed934b384f16e8c01b185b082d6b5ffc7dcbb0b6a6eb48cd465282d957", size = 11918995 }, + { url = "https://files.pythonhosted.org/packages/ac/33/b8eb3acc67bbca4d9872fc9ff94db78e6167a7ba5cd932f585d1560effc7/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1aa6796edcc3c8d163c9e39c429d50076d266d68980fed9d1b2443f617c67e9e", size = 16844162 }, + { url = "https://files.pythonhosted.org/packages/80/11/6474893b07121057035069a0a483fe1cd8c47878213f282afb4c0c6fc275/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24c0482c51726430fb83724451921c0e539d769c8618dcfd46b1645e7f75960d", size = 38966728 }, + { url = "https://files.pythonhosted.org/packages/94/88/8fc7ff435c5e783e5fad9586d839d463e023988dbbbad949d442092d01f1/ctranslate2-4.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:76db234c0446a23d20dd8eeaa7a789cc87d1d05283f48bf3152bae9fa0a69844", size = 19100788 }, + { url = "https://files.pythonhosted.org/packages/d9/b3/f100013a76a98d64e67c721bd4559ea4eeb54be3e4ac45f4d801769899af/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:058c9db2277dc8b19ecc86c7937628f69022f341844b9081d2ab642965d88fc6", size = 1280179 }, + { url = "https://files.pythonhosted.org/packages/39/22/b77f748015667a5e2ca54a5ee080d7016fce34314f0e8cf904784549305a/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:5abcf885062c7f28a3f9a46be8d185795e8706ac6230ad086cae0bc82917df31", size = 11940166 }, + { url = "https://files.pythonhosted.org/packages/7d/78/6d7fd52f646c6ba3343f71277a9bbef33734632949d1651231948b0f0359/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9950acb04a002d5c60ae90a1ddceead1a803af1f00cadd9b1a1dc76e1f017481", size = 16849483 }, + { url = "https://files.pythonhosted.org/packages/40/27/58769ff15ac31b44205bd7a8aeca80cf7357c657ea5df1b94ce0f5c83771/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1dcc734e92e3f1ceeaa0c42bbfd009352857be179ecd4a7ed6cccc086a202f58", size = 38949393 }, + { url = "https://files.pythonhosted.org/packages/0e/5c/9fa0ad6462b62efd0fb5ac1100eee47bc96ecc198ff4e237c731e5473616/ctranslate2-4.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dfb7657bdb7b8211c8f9ecb6f3b70bc0db0e0384d01a8b1808cb66fe7199df59", size = 19123451 }, ] [[package]] @@ -1386,14 +1386,14 @@ dependencies = [ { name = "cuda-pathfinder", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, - { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, - { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, - { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404 }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619 }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610 }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914 }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673 }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386 }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469 }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693 }, ] [[package]] @@ -1401,7 +1401,7 @@ name = "cuda-pathfinder" version = "1.5.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739, upload-time = "2026-03-24T21:14:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739 }, ] [[package]] @@ -1409,7 +1409,7 @@ name = "cuda-toolkit" version = "13.0.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364 }, ] [package.optional-dependencies] @@ -1454,13 +1454,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/fa/b2d55f0d53c7c7f5dc0b6dbb48cc4344ee84fb572f23de28040bf2cde89d/curated-tokenizers-0.0.9.tar.gz", hash = "sha256:c93d47e54ab3528a6db2796eeb4bdce5d44e8226c671e42c2f23522ab1d0ce25", size = 2237055, upload-time = "2024-01-18T13:45:52.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/fa/b2d55f0d53c7c7f5dc0b6dbb48cc4344ee84fb572f23de28040bf2cde89d/curated-tokenizers-0.0.9.tar.gz", hash = "sha256:c93d47e54ab3528a6db2796eeb4bdce5d44e8226c671e42c2f23522ab1d0ce25", size = 2237055 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/3e/c10474a21ed0166f94cebb46fe96cf07fdf7f399d84e6157ec4dfbd97b53/curated_tokenizers-0.0.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e66aedfeae0c91f3f3e2980b17933b3d08f3fba6c8ba7057b9b05d596e8a0b27", size = 734544, upload-time = "2024-01-18T13:45:18.864Z" }, - { url = "https://files.pythonhosted.org/packages/34/fb/d6e57b1155bee398f43de58ecdcdda44957e9635183312ac0820a19fc94d/curated_tokenizers-0.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2abbb571666a9c9b3a15f9df022e25ed1137e9fa8346788aaa747c00f940a3c6", size = 703466, upload-time = "2024-01-18T13:45:20.051Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7c/2d24633275f2854c144652ee6ef97ae85d444855b6da5aa1203678541fa5/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64b9991a9720a0ce8cc72d29791fd73f2cc2bef0241b002fd2a756ec8a629143", size = 706194, upload-time = "2024-01-18T13:45:21.844Z" }, - { url = "https://files.pythonhosted.org/packages/21/24/12ae8f92d0e319ed07dd9c3ee5d24e71dd6ff3dd8d4dbe2126a6e5cbf7a1/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fb208a01f2b3f22172596915d229859549a2d76e484be976dd728b1ca3bdec", size = 734029, upload-time = "2024-01-18T13:45:23.628Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fc/776b7464029ea126bf55df2a5edd437178ad8e5c0126f953891dfa603f9c/curated_tokenizers-0.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:209d756694c7fb000a0b642016eb6e71c740cfce293adcbf3384aa2a1e701eb2", size = 731507, upload-time = "2024-01-18T13:45:25.416Z" }, + { url = "https://files.pythonhosted.org/packages/10/3e/c10474a21ed0166f94cebb46fe96cf07fdf7f399d84e6157ec4dfbd97b53/curated_tokenizers-0.0.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e66aedfeae0c91f3f3e2980b17933b3d08f3fba6c8ba7057b9b05d596e8a0b27", size = 734544 }, + { url = "https://files.pythonhosted.org/packages/34/fb/d6e57b1155bee398f43de58ecdcdda44957e9635183312ac0820a19fc94d/curated_tokenizers-0.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2abbb571666a9c9b3a15f9df022e25ed1137e9fa8346788aaa747c00f940a3c6", size = 703466 }, + { url = "https://files.pythonhosted.org/packages/1b/7c/2d24633275f2854c144652ee6ef97ae85d444855b6da5aa1203678541fa5/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64b9991a9720a0ce8cc72d29791fd73f2cc2bef0241b002fd2a756ec8a629143", size = 706194 }, + { url = "https://files.pythonhosted.org/packages/21/24/12ae8f92d0e319ed07dd9c3ee5d24e71dd6ff3dd8d4dbe2126a6e5cbf7a1/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fb208a01f2b3f22172596915d229859549a2d76e484be976dd728b1ca3bdec", size = 734029 }, + { url = "https://files.pythonhosted.org/packages/0f/fc/776b7464029ea126bf55df2a5edd437178ad8e5c0126f953891dfa603f9c/curated_tokenizers-0.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:209d756694c7fb000a0b642016eb6e71c740cfce293adcbf3384aa2a1e701eb2", size = 731507 }, ] [[package]] @@ -1470,66 +1470,66 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/06/6c12c149a7f737dacc76b4c3949dbc7ff87d622567b86996896ae4d104aa/curated-transformers-0.1.1.tar.gz", hash = "sha256:4671f03314df30efda2ec2b59bc7692ea34fcea44cb65382342c16684e8a2119", size = 16313, upload-time = "2023-05-24T07:29:22.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/06/6c12c149a7f737dacc76b4c3949dbc7ff87d622567b86996896ae4d104aa/curated-transformers-0.1.1.tar.gz", hash = "sha256:4671f03314df30efda2ec2b59bc7692ea34fcea44cb65382342c16684e8a2119", size = 16313 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/67/3b72b3fdfcadab61bc8f59c17e63770e526ffabd583ed32f174a7c01af85/curated_transformers-0.1.1-py2.py3-none-any.whl", hash = "sha256:d716063d73d803c6925d2dab56fde9b9ab8e89e663c2c0587804944ba488ff01", size = 25972, upload-time = "2023-05-24T07:29:21.119Z" }, + { url = "https://files.pythonhosted.org/packages/d6/67/3b72b3fdfcadab61bc8f59c17e63770e526ffabd583ed32f174a7c01af85/curated_transformers-0.1.1-py2.py3-none-any.whl", hash = "sha256:d716063d73d803c6925d2dab56fde9b9ab8e89e663c2c0587804944ba488ff01", size = 25972 }, ] [[package]] name = "cycler" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] [[package]] name = "cymem" version = "2.0.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320, upload-time = "2025-11-14T14:58:36.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745, upload-time = "2025-11-14T14:57:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927, upload-time = "2025-11-14T14:57:33.827Z" }, - { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346, upload-time = "2025-11-14T14:57:34.917Z" }, - { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843, upload-time = "2025-11-14T14:57:36.503Z" }, - { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607, upload-time = "2025-11-14T14:57:38.036Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421, upload-time = "2025-11-14T14:57:39.265Z" }, - { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176, upload-time = "2025-11-14T14:57:40.706Z" }, - { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959, upload-time = "2025-11-14T14:57:41.682Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478, upload-time = "2025-11-14T14:57:42.682Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695, upload-time = "2025-11-14T14:57:43.741Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573, upload-time = "2025-11-14T14:57:44.81Z" }, - { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572, upload-time = "2025-11-14T14:57:46.023Z" }, - { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060, upload-time = "2025-11-14T14:57:47.605Z" }, - { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601, upload-time = "2025-11-14T14:57:48.861Z" }, - { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103, upload-time = "2025-11-14T14:57:50.396Z" }, - { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016, upload-time = "2025-11-14T14:57:51.611Z" }, - { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429, upload-time = "2025-11-14T14:57:52.582Z" }, - { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205, upload-time = "2025-11-14T14:57:53.64Z" }, - { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083, upload-time = "2025-11-14T14:57:55.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159, upload-time = "2025-11-14T14:57:57.106Z" }, - { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186, upload-time = "2025-11-14T14:57:58.334Z" }, - { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353, upload-time = "2025-11-14T14:58:00.562Z" }, - { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764, upload-time = "2025-11-14T14:58:01.793Z" }, - { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964, upload-time = "2025-11-14T14:58:02.87Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812, upload-time = "2025-11-14T14:58:04.227Z" }, - { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951, upload-time = "2025-11-14T14:58:05.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878, upload-time = "2025-11-14T14:58:06.95Z" }, - { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571, upload-time = "2025-11-14T14:58:08.232Z" }, - { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555, upload-time = "2025-11-14T14:58:09.429Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177, upload-time = "2025-11-14T14:58:10.594Z" }, - { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853, upload-time = "2025-11-14T14:58:12.116Z" }, - { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970, upload-time = "2025-11-14T14:58:13.114Z" }, - { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804, upload-time = "2025-11-14T14:58:14.113Z" }, - { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254, upload-time = "2025-11-14T14:58:15.156Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061, upload-time = "2025-11-14T14:58:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784, upload-time = "2025-11-14T14:58:17.412Z" }, - { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062, upload-time = "2025-11-14T14:58:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465, upload-time = "2025-11-14T14:58:21.815Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665, upload-time = "2025-11-14T14:58:23.512Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715, upload-time = "2025-11-14T14:58:24.773Z" }, + { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745 }, + { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927 }, + { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346 }, + { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843 }, + { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607 }, + { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421 }, + { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176 }, + { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959 }, + { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478 }, + { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695 }, + { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573 }, + { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572 }, + { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060 }, + { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601 }, + { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103 }, + { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016 }, + { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429 }, + { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205 }, + { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083 }, + { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159 }, + { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186 }, + { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353 }, + { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764 }, + { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964 }, + { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812 }, + { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951 }, + { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878 }, + { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571 }, + { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555 }, + { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177 }, + { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853 }, + { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970 }, + { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804 }, + { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254 }, + { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061 }, + { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784 }, + { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062 }, + { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465 }, + { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665 }, + { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715 }, ] [[package]] @@ -1540,9 +1540,9 @@ dependencies = [ { name = "marshmallow" }, { name = "typing-inspect" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, ] [[package]] @@ -1564,9 +1564,9 @@ dependencies = [ { name = "tqdm" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/9d/348ed92110ba5f9b70b51ca1078d4809767a835aa2b7ce7e74ad2b98323d/datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1", size = 569566, upload-time = "2025-07-09T14:35:52.431Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/9d/348ed92110ba5f9b70b51ca1078d4809767a835aa2b7ce7e74ad2b98323d/datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1", size = 569566 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/62/eb8157afb21bd229c864521c1ab4fa8e9b4f1b06bafdd8c4668a7a31b5dd/datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d", size = 494825, upload-time = "2025-07-09T14:35:50.658Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/eb8157afb21bd229c864521c1ab4fa8e9b4f1b06bafdd8c4668a7a31b5dd/datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d", size = 494825 }, ] [[package]] @@ -1579,9 +1579,9 @@ dependencies = [ { name = "regex" }, { name = "tzlocal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/2d/a0ccdb78788064fa0dc901b8524e50615c42be1d78b78d646d0b28d09180/dateparser-1.4.0.tar.gz", hash = "sha256:97a21840d5ecdf7630c584f673338a5afac5dfe84f647baf4d7e8df98f9354a4", size = 321512, upload-time = "2026-03-26T09:56:10.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/2d/a0ccdb78788064fa0dc901b8524e50615c42be1d78b78d646d0b28d09180/dateparser-1.4.0.tar.gz", hash = "sha256:97a21840d5ecdf7630c584f673338a5afac5dfe84f647baf4d7e8df98f9354a4", size = 321512 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/0b/3c3bb7cbe757279e693a0be6049048012f794d01f81099609ecd53b899f0/dateparser-1.4.0-py3-none-any.whl", hash = "sha256:7902b8e85d603494bf70a5a0b1decdddb2270b9c6e6b2bc8a57b93476c0df378", size = 300379, upload-time = "2026-03-26T09:56:08.409Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0b/3c3bb7cbe757279e693a0be6049048012f794d01f81099609ecd53b899f0/dateparser-1.4.0-py3-none-any.whl", hash = "sha256:7902b8e85d603494bf70a5a0b1decdddb2270b9c6e6b2bc8a57b93476c0df378", size = 300379 }, ] [[package]] @@ -1608,9 +1608,9 @@ dependencies = [ { name = "urllib3" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/03/b07a337cfba22d8cea1384c71e806361180c91bac3ce10320caf709153af/daytona-0.158.0.tar.gz", hash = "sha256:cddc54228727221a1f892f6a09df79eb5545f44f442544f0c97d48664c3c72b0", size = 127196, upload-time = "2026-03-27T14:39:08.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/03/b07a337cfba22d8cea1384c71e806361180c91bac3ce10320caf709153af/daytona-0.158.0.tar.gz", hash = "sha256:cddc54228727221a1f892f6a09df79eb5545f44f442544f0c97d48664c3c72b0", size = 127196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/c2/ca914fc4574f96182d6367293734119bc4114535780666af22c7e56357db/daytona-0.158.0-py3-none-any.whl", hash = "sha256:c9dbb02143e9d1d43bffc13bc22907f352d7df9d704cf8e018cbab4832bf5d0f", size = 157792, upload-time = "2026-03-27T14:39:06.405Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c2/ca914fc4574f96182d6367293734119bc4114535780666af22c7e56357db/daytona-0.158.0-py3-none-any.whl", hash = "sha256:c9dbb02143e9d1d43bffc13bc22907f352d7df9d704cf8e018cbab4832bf5d0f", size = 157792 }, ] [[package]] @@ -1623,9 +1623,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/bf/a0b60190134a0a85fc113c32592447a6e51044dd7cc817345418e860493c/daytona_api_client-0.158.0.tar.gz", hash = "sha256:4751383cf62e5bd9746037b3190f62c7fa2690231f04ce69fd2892e6e70a05db", size = 145997, upload-time = "2026-03-27T14:38:13.024Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/bf/a0b60190134a0a85fc113c32592447a6e51044dd7cc817345418e860493c/daytona_api_client-0.158.0.tar.gz", hash = "sha256:4751383cf62e5bd9746037b3190f62c7fa2690231f04ce69fd2892e6e70a05db", size = 145997 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/aa/5b10ed8349c370cf9c42d208302a8e5d7d813a3f3b0195510c412400c0f3/daytona_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2ddec78fae84a8afe5a894dab7c35f31f8448f48282a5c93c03bf974f1119390", size = 400550, upload-time = "2026-03-27T14:38:11.275Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/5b10ed8349c370cf9c42d208302a8e5d7d813a3f3b0195510c412400c0f3/daytona_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2ddec78fae84a8afe5a894dab7c35f31f8448f48282a5c93c03bf974f1119390", size = 400550 }, ] [[package]] @@ -1640,9 +1640,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/b2/053a262c2fcc1b216dc6518a1a15dff7e0b6686af2533fb41d1aa36a40cc/daytona_api_client_async-0.158.0.tar.gz", hash = "sha256:8d06230e211204b6e4b994a41ce94890007fbe11fa55c99386ba547e40624431", size = 146115, upload-time = "2026-03-27T14:38:15.64Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/b2/053a262c2fcc1b216dc6518a1a15dff7e0b6686af2533fb41d1aa36a40cc/daytona_api_client_async-0.158.0.tar.gz", hash = "sha256:8d06230e211204b6e4b994a41ce94890007fbe11fa55c99386ba547e40624431", size = 146115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/87/ed12a2caefd8a87a1266b82e195b5c354b43ac81252c4e0098a9cd25e975/daytona_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:9247809870d02a355e8136e4b6ee041fec371e01ed03deff1b9f20027f69c3fe", size = 403611, upload-time = "2026-03-27T14:38:13.925Z" }, + { url = "https://files.pythonhosted.org/packages/0c/87/ed12a2caefd8a87a1266b82e195b5c354b43ac81252c4e0098a9cd25e975/daytona_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:9247809870d02a355e8136e4b6ee041fec371e01ed03deff1b9f20027f69c3fe", size = 403611 }, ] [[package]] @@ -1655,9 +1655,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/9a/1fa332a4ffa5bbbf95ef946a98ed590fe2c7ab58727065e381eebacfb7aa/daytona_toolbox_api_client-0.158.0.tar.gz", hash = "sha256:2abd2d7521772af3089fd2d36142c5f2d0ffb0f2aa12cf5134ae5274161342f0", size = 67495, upload-time = "2026-03-27T14:38:20.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/9a/1fa332a4ffa5bbbf95ef946a98ed590fe2c7ab58727065e381eebacfb7aa/daytona_toolbox_api_client-0.158.0.tar.gz", hash = "sha256:2abd2d7521772af3089fd2d36142c5f2d0ffb0f2aa12cf5134ae5274161342f0", size = 67495 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/dc/ed944ce6511e092d46831485580180f8caecfbca32bfe39fc92f1a3936a1/daytona_toolbox_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2852d161aae238aa08e5a0c91b6dd2861521b7a780467216ce97fdf5e4f85f22", size = 177288, upload-time = "2026-03-27T14:38:19.294Z" }, + { url = "https://files.pythonhosted.org/packages/36/dc/ed944ce6511e092d46831485580180f8caecfbca32bfe39fc92f1a3936a1/daytona_toolbox_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2852d161aae238aa08e5a0c91b6dd2861521b7a780467216ce97fdf5e4f85f22", size = 177288 }, ] [[package]] @@ -1672,9 +1672,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/b2/e72bbbb0e70855befad02863e42b4fc2c4944ce75acbbad1e1ff8780b3ed/daytona_toolbox_api_client_async-0.158.0.tar.gz", hash = "sha256:d49c7f4372881a0c521ab6e63a57c97163795f717dbaeb072afc5e377ef50f33", size = 64560, upload-time = "2026-03-27T14:38:36.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/b2/e72bbbb0e70855befad02863e42b4fc2c4944ce75acbbad1e1ff8780b3ed/daytona_toolbox_api_client_async-0.158.0.tar.gz", hash = "sha256:d49c7f4372881a0c521ab6e63a57c97163795f717dbaeb072afc5e377ef50f33", size = 64560 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/db/95e931deb1b131e522e116fc3462b3f61169cbf6acba011fffcda94ca38a/daytona_toolbox_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:c3c1ab2a26e819e76d2c66d6bad49360200d9aa9aa945eed6b74e8a2d210e43d", size = 178684, upload-time = "2026-03-27T14:38:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/db/db/95e931deb1b131e522e116fc3462b3f61169cbf6acba011fffcda94ca38a/daytona_toolbox_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:c3c1ab2a26e819e76d2c66d6bad49360200d9aa9aa945eed6b74e8a2d210e43d", size = 178684 }, ] [[package]] @@ -1688,18 +1688,18 @@ dependencies = [ { name = "langchain-google-genai" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/0b2ccd5e4f40c1554145de17a7f3ee41994de1dda3ea36abe28600f1a3cf/deepagents-0.4.12.tar.gz", hash = "sha256:fc24a691e5cba00920ac4fa1d94f8147d6081fe513ed22bdba7da469288681c3", size = 91870, upload-time = "2026-03-20T14:54:29.904Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/0b2ccd5e4f40c1554145de17a7f3ee41994de1dda3ea36abe28600f1a3cf/deepagents-0.4.12.tar.gz", hash = "sha256:fc24a691e5cba00920ac4fa1d94f8147d6081fe513ed22bdba7da469288681c3", size = 91870 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/77/63a5cb4e3a8871c4a52d600661a232c26b35f52931ee551c3adc38eeacf6/deepagents-0.4.12-py3-none-any.whl", hash = "sha256:76a272bac25607c5ef8c5adc876e391da945f1107b504686964dfdb6afdc1ebb", size = 104455, upload-time = "2026-03-20T14:54:28.786Z" }, + { url = "https://files.pythonhosted.org/packages/5e/77/63a5cb4e3a8871c4a52d600661a232c26b35f52931ee551c3adc38eeacf6/deepagents-0.4.12-py3-none-any.whl", hash = "sha256:76a272bac25607c5ef8c5adc876e391da945f1107b504686964dfdb6afdc1ebb", size = 104455 }, ] [[package]] name = "defusedxml" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, ] [[package]] @@ -1709,9 +1709,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998 }, ] [[package]] @@ -1721,27 +1721,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, ] [[package]] name = "dill" version = "0.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847, upload-time = "2024-01-27T23:42:16.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252, upload-time = "2024-01-27T23:42:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, ] [[package]] name = "dirtyjson" version = "1.0.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782, upload-time = "2022-11-28T23:32:33.319Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197, upload-time = "2022-11-28T23:32:31.219Z" }, + { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197 }, ] [[package]] @@ -1752,36 +1752,36 @@ dependencies = [ { name = "aiohttp" }, { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/57/9a2d9abdabdc9db8ef28ce0cf4129669e1c8717ba28d607b5ba357c4de3b/discord_py-2.7.1.tar.gz", hash = "sha256:24d5e6a45535152e4b98148a9dd6b550d25dc2c9fb41b6d670319411641249da", size = 1106326, upload-time = "2026-03-03T18:40:46.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/57/9a2d9abdabdc9db8ef28ce0cf4129669e1c8717ba28d607b5ba357c4de3b/discord_py-2.7.1.tar.gz", hash = "sha256:24d5e6a45535152e4b98148a9dd6b550d25dc2c9fb41b6d670319411641249da", size = 1106326 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/a7/17208c3b3f92319e7fad259f1c6d5a5baf8fd0654c54846ced329f83c3eb/discord_py-2.7.1-py3-none-any.whl", hash = "sha256:849dca2c63b171146f3a7f3f8acc04248098e9e6203412ce3cf2745f284f7439", size = 1227550, upload-time = "2026-03-03T18:40:44.492Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/17208c3b3f92319e7fad259f1c6d5a5baf8fd0654c54846ced329f83c3eb/discord_py-2.7.1-py3-none-any.whl", hash = "sha256:849dca2c63b171146f3a7f3f8acc04248098e9e6203412ce3cf2745f284f7439", size = 1227550 }, ] [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, ] [[package]] name = "dlinfo" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/8e/8f2f94cd40af1b51e8e371a83b385d622170d42f98776441a6118f4dd682/dlinfo-2.0.0.tar.gz", hash = "sha256:88a2bc04f51d01bc604cdc9eb1c3cc0bde89057532ca6a3e71a41f6235433e17", size = 12727, upload-time = "2025-01-16T15:43:10.756Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/8e/8f2f94cd40af1b51e8e371a83b385d622170d42f98776441a6118f4dd682/dlinfo-2.0.0.tar.gz", hash = "sha256:88a2bc04f51d01bc604cdc9eb1c3cc0bde89057532ca6a3e71a41f6235433e17", size = 12727 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/90/022c79d6e5e6f843268c10b84d4a021ee3afba0621d3c176d3ff2024bfc8/dlinfo-2.0.0-py3-none-any.whl", hash = "sha256:b32cc18e3ea67c0ca9ca409e5b41eed863bd1363dbc9dd3de90fedf11b61e7bc", size = 3654, upload-time = "2025-01-16T15:43:09.474Z" }, + { url = "https://files.pythonhosted.org/packages/da/90/022c79d6e5e6f843268c10b84d4a021ee3afba0621d3c176d3ff2024bfc8/dlinfo-2.0.0-py3-none-any.whl", hash = "sha256:b32cc18e3ea67c0ca9ca409e5b41eed863bd1363dbc9dd3de90fedf11b61e7bc", size = 3654 }, ] [[package]] name = "dnspython" version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094 }, ] [[package]] @@ -1821,9 +1821,9 @@ dependencies = [ { name = "tqdm" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/de/d38c5b258517a142262080b481121c4fdd1b356525327c7c225bdda5fb42/docling-2.82.0.tar.gz", hash = "sha256:a5fbc7520ece4705a7416fd7d00583855a640bea823bef6d326bda8621791b35", size = 417279, upload-time = "2026-03-25T09:41:08.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/de/d38c5b258517a142262080b481121c4fdd1b356525327c7c225bdda5fb42/docling-2.82.0.tar.gz", hash = "sha256:a5fbc7520ece4705a7416fd7d00583855a640bea823bef6d326bda8621791b35", size = 417279 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/5d/b1fc590ad0d1290a309c589f795a768db78a2b09d4bb4609829fb7fc71a1/docling-2.82.0-py3-none-any.whl", hash = "sha256:19c6fcff1832a7b21604a680360a4600f5ac83fcbcb391b095c018faac8a6805", size = 446283, upload-time = "2026-03-25T09:41:06.164Z" }, + { url = "https://files.pythonhosted.org/packages/26/5d/b1fc590ad0d1290a309c589f795a768db78a2b09d4bb4609829fb7fc71a1/docling-2.82.0-py3-none-any.whl", hash = "sha256:19c6fcff1832a7b21604a680360a4600f5ac83fcbcb391b095c018faac8a6805", size = 446283 }, ] [[package]] @@ -1843,9 +1843,9 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/fb/2d88db67502418bb3e04e7ed2ac1893510d0c93c33defef08a6047d705e8/docling_core-2.70.2.tar.gz", hash = "sha256:f4cf53c86afc0f14bd526f9ee94125f68f77a284a079a99d1b83c9368ab5508c", size = 300733, upload-time = "2026-03-20T15:38:12.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/fb/2d88db67502418bb3e04e7ed2ac1893510d0c93c33defef08a6047d705e8/docling_core-2.70.2.tar.gz", hash = "sha256:f4cf53c86afc0f14bd526f9ee94125f68f77a284a079a99d1b83c9368ab5508c", size = 300733 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/d1/cc3ff3e9afd17447510270622429eab6e0edc94f0c842f463075f20c3cab/docling_core-2.70.2-py3-none-any.whl", hash = "sha256:80d456b3500654f5ded421bd08d10bf255d76167c2d7f5ca7b0db1c8eed53a0a", size = 266702, upload-time = "2026-03-20T15:38:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d1/cc3ff3e9afd17447510270622429eab6e0edc94f0c842f463075f20c3cab/docling_core-2.70.2-py3-none-any.whl", hash = "sha256:80d456b3500654f5ded421bd08d10bf255d76167c2d7f5ca7b0db1c8eed53a0a", size = 266702 }, ] [package.optional-dependencies] @@ -1878,9 +1878,9 @@ dependencies = [ { name = "tqdm" }, { name = "transformers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/87/01bf0c710af37328aa3517b34e64c2a2f3a6283a1cfc8859ae05881dd769/docling_ibm_models-3.13.0.tar.gz", hash = "sha256:f402effae8a63b0e5c3b5ce13120601baa2cd8098beef1d53ab5a056443758d3", size = 98538, upload-time = "2026-03-27T15:49:57.569Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/87/01bf0c710af37328aa3517b34e64c2a2f3a6283a1cfc8859ae05881dd769/docling_ibm_models-3.13.0.tar.gz", hash = "sha256:f402effae8a63b0e5c3b5ce13120601baa2cd8098beef1d53ab5a056443758d3", size = 98538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/52/11a8c8fff80e1fa581173edcc91cc92ed24184519e746fe39456f617653d/docling_ibm_models-3.13.0-py3-none-any.whl", hash = "sha256:a11acc6034b06e0bed8dc0ca1fa700615b8246eacce411619168e1f6562b0d0d", size = 93855, upload-time = "2026-03-27T15:49:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/25/52/11a8c8fff80e1fa581173edcc91cc92ed24184519e746fe39456f617653d/docling_ibm_models-3.13.0-py3-none-any.whl", hash = "sha256:a11acc6034b06e0bed8dc0ca1fa700615b8246eacce411619168e1f6562b0d0d", size = 93855 }, ] [[package]] @@ -1894,53 +1894,53 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tabulate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/34/f951e261d20cc71bc55703a3f4f51b13d8dc98ed634995905ecc41e5650a/docling_parse-5.6.1.tar.gz", hash = "sha256:e47d40a7c268a775c41a8cc7773555a5856a1bcfd5a05ee97ee0a162270ad7f9", size = 61127846, upload-time = "2026-03-24T11:15:01.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/34/f951e261d20cc71bc55703a3f4f51b13d8dc98ed634995905ecc41e5650a/docling_parse-5.6.1.tar.gz", hash = "sha256:e47d40a7c268a775c41a8cc7773555a5856a1bcfd5a05ee97ee0a162270ad7f9", size = 61127846 } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/70/b91d5630b736d56c2fb4b0480ae364fedf0ce0735ef52408c2a915e4657d/docling_parse-5.6.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7b978cfb4f8c08c81202a611aefb45ff2cf5a0ff02c27f390851f749110087ec", size = 7807265, upload-time = "2026-03-24T11:14:34.827Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0f/3b9ecfab5d881ffda30bfc950854175660cb852428ddecc49cac1bc56bc7/docling_parse-5.6.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe78cc80489f79ed4ef5ba7e56f6e280d64c1be86b9b188b7e669748e09098bc", size = 8202963, upload-time = "2026-03-24T11:14:36.508Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6b/d434c3328ce45e8847883cf409c117677003c883d08a7de8a09fd71e959d/docling_parse-5.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5317adacca100a1a2ed85040e4b38552b451de56304c0c63c5be2ddf448d752c", size = 8302625, upload-time = "2026-03-24T11:14:38.306Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/df0e86122456345c81f81cec93aec49a0c1283e14c3787f95666b29a3a83/docling_parse-5.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f51cc4ebbe7aa34511dc5ed34186a9253e95c3ee91fa462ce5775f3d56764fe", size = 9208290, upload-time = "2026-03-24T11:14:40.381Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/e3238fb0f8eef299121d58a889564b97b0b5486301c0ffb776c0e4e7bd51/docling_parse-5.6.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6eb7c1caab1f78952b6daa9b09448a052408eb44f528d17f38a27fe3c048616a", size = 7807330, upload-time = "2026-03-24T11:14:42.621Z" }, - { url = "https://files.pythonhosted.org/packages/b7/1e/840a855a0f0e85c339a7e5b7dc7aa2eee191550dca64c59da45cad37b2cd/docling_parse-5.6.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d0f0a8fe0552430aee6581f4f021def72e0842652696c6b295aca8f5e2712c8", size = 8203763, upload-time = "2026-03-24T11:14:44.649Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ea/d010a8c679656e1274f7c08836eb765a3366076889890e80a1690daa686f/docling_parse-5.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:630da916fcd8cb7dae92143bd5f2a571827f75a4baaf8818d04521334379c32b", size = 8302890, upload-time = "2026-03-24T11:14:46.876Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ec/231bd25fc104e9c6cd5fbb48f5a9df199c6f3ced950968f67887c85e131b/docling_parse-5.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:d5cba3c5102936b3ebbe983cd15fa250bf3b57c9ced65a3eb6b53f15d2d68378", size = 9208256, upload-time = "2026-03-24T11:14:48.692Z" }, - { url = "https://files.pythonhosted.org/packages/53/d3/95acf75c9cafae1fe3ca85fe303c852c98f235c18eb4ce631709644da34b/docling_parse-5.6.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fd46e42d9f6cc30d3ed2f2c0046c529926852b772cebe9802e61c07019c1b5ce", size = 7807909, upload-time = "2026-03-24T11:14:50.87Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d1/26aeadf8b666646445d401183ef16ffe6c71044817f2f7cb3110c0367b63/docling_parse-5.6.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6f0c43d30bc2188a9b700587cd9ab4e3e088aa4a8f033fd50281e205603d28a", size = 8204294, upload-time = "2026-03-24T11:14:53.049Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/1c93f3fe0574ff909b6a5edbd2f34f972c9f7102c30e97a72ce3d7c448e2/docling_parse-5.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b0cadcad4433718d569219e2e19547dfea4ba6b803f35658e9cd02db52480ec", size = 8303207, upload-time = "2026-03-24T11:14:54.785Z" }, - { url = "https://files.pythonhosted.org/packages/70/36/cc7a4a2697b8c9e3660811637f67e4880a97c16551376e630fdfee3eac3a/docling_parse-5.6.1-cp314-cp314-win_amd64.whl", hash = "sha256:d7ac7fbd6012d83e3a7845e618ec67854bccad09ab097219418c167a1fb9eef5", size = 9569000, upload-time = "2026-03-24T11:14:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/21/70/b91d5630b736d56c2fb4b0480ae364fedf0ce0735ef52408c2a915e4657d/docling_parse-5.6.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7b978cfb4f8c08c81202a611aefb45ff2cf5a0ff02c27f390851f749110087ec", size = 7807265 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/3b9ecfab5d881ffda30bfc950854175660cb852428ddecc49cac1bc56bc7/docling_parse-5.6.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe78cc80489f79ed4ef5ba7e56f6e280d64c1be86b9b188b7e669748e09098bc", size = 8202963 }, + { url = "https://files.pythonhosted.org/packages/3c/6b/d434c3328ce45e8847883cf409c117677003c883d08a7de8a09fd71e959d/docling_parse-5.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5317adacca100a1a2ed85040e4b38552b451de56304c0c63c5be2ddf448d752c", size = 8302625 }, + { url = "https://files.pythonhosted.org/packages/ad/04/df0e86122456345c81f81cec93aec49a0c1283e14c3787f95666b29a3a83/docling_parse-5.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f51cc4ebbe7aa34511dc5ed34186a9253e95c3ee91fa462ce5775f3d56764fe", size = 9208290 }, + { url = "https://files.pythonhosted.org/packages/05/1b/e3238fb0f8eef299121d58a889564b97b0b5486301c0ffb776c0e4e7bd51/docling_parse-5.6.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6eb7c1caab1f78952b6daa9b09448a052408eb44f528d17f38a27fe3c048616a", size = 7807330 }, + { url = "https://files.pythonhosted.org/packages/b7/1e/840a855a0f0e85c339a7e5b7dc7aa2eee191550dca64c59da45cad37b2cd/docling_parse-5.6.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d0f0a8fe0552430aee6581f4f021def72e0842652696c6b295aca8f5e2712c8", size = 8203763 }, + { url = "https://files.pythonhosted.org/packages/fc/ea/d010a8c679656e1274f7c08836eb765a3366076889890e80a1690daa686f/docling_parse-5.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:630da916fcd8cb7dae92143bd5f2a571827f75a4baaf8818d04521334379c32b", size = 8302890 }, + { url = "https://files.pythonhosted.org/packages/d8/ec/231bd25fc104e9c6cd5fbb48f5a9df199c6f3ced950968f67887c85e131b/docling_parse-5.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:d5cba3c5102936b3ebbe983cd15fa250bf3b57c9ced65a3eb6b53f15d2d68378", size = 9208256 }, + { url = "https://files.pythonhosted.org/packages/53/d3/95acf75c9cafae1fe3ca85fe303c852c98f235c18eb4ce631709644da34b/docling_parse-5.6.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fd46e42d9f6cc30d3ed2f2c0046c529926852b772cebe9802e61c07019c1b5ce", size = 7807909 }, + { url = "https://files.pythonhosted.org/packages/6a/d1/26aeadf8b666646445d401183ef16ffe6c71044817f2f7cb3110c0367b63/docling_parse-5.6.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6f0c43d30bc2188a9b700587cd9ab4e3e088aa4a8f033fd50281e205603d28a", size = 8204294 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/1c93f3fe0574ff909b6a5edbd2f34f972c9f7102c30e97a72ce3d7c448e2/docling_parse-5.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b0cadcad4433718d569219e2e19547dfea4ba6b803f35658e9cd02db52480ec", size = 8303207 }, + { url = "https://files.pythonhosted.org/packages/70/36/cc7a4a2697b8c9e3660811637f67e4880a97c16551376e630fdfee3eac3a/docling_parse-5.6.1-cp314-cp314-win_amd64.whl", hash = "sha256:d7ac7fbd6012d83e3a7845e618ec67854bccad09ab097219418c167a1fb9eef5", size = 9569000 }, ] [[package]] name = "docopt" version = "0.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901 } [[package]] name = "docstring-parser" version = "0.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896 }, ] [[package]] name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196 }, ] [[package]] name = "durationpy" version = "0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922 }, ] [[package]] @@ -1952,9 +1952,9 @@ dependencies = [ { name = "sniffio" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/0a/a92140b666afdcb9862a16e4d80873b3c887c1b7e3f17e945fc3460edf1b/elastic_transport-9.2.1.tar.gz", hash = "sha256:97d9abd638ba8aa90faa4ca1bf1a18bde0fe2088fbc8757f2eb7b299f205773d", size = 77403, upload-time = "2025-12-23T11:54:12.849Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/0a/a92140b666afdcb9862a16e4d80873b3c887c1b7e3f17e945fc3460edf1b/elastic_transport-9.2.1.tar.gz", hash = "sha256:97d9abd638ba8aa90faa4ca1bf1a18bde0fe2088fbc8757f2eb7b299f205773d", size = 77403 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e6/a42b600ae8b808371f740381f6c32050cad93f870d36cc697b8b7006bf7c/elastic_transport-9.2.1-py3-none-any.whl", hash = "sha256:39e1a25e486af34ce7aa1bc9005d1c736f1b6fb04c9b64ea0604ded5a61fc1d4", size = 65327, upload-time = "2025-12-23T11:54:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e6/a42b600ae8b808371f740381f6c32050cad93f870d36cc697b8b7006bf7c/elastic_transport-9.2.1-py3-none-any.whl", hash = "sha256:39e1a25e486af34ce7aa1bc9005d1c736f1b6fb04c9b64ea0604ded5a61fc1d4", size = 65327 }, ] [[package]] @@ -1968,9 +1968,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/15/283459c9299d412ffa2aaab69b082857631c519233f5491d6c567e3320ca/elasticsearch-9.3.0.tar.gz", hash = "sha256:f76e149c0a22d5ccbba58bdc30c9f51cf894231b359ef4fd7e839b558b59f856", size = 893538, upload-time = "2026-02-03T20:26:38.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/15/283459c9299d412ffa2aaab69b082857631c519233f5491d6c567e3320ca/elasticsearch-9.3.0.tar.gz", hash = "sha256:f76e149c0a22d5ccbba58bdc30c9f51cf894231b359ef4fd7e839b558b59f856", size = 893538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/37/3a196f8918743f2104cb66b1f56218079ecac6e128c061de7df7f4faef02/elasticsearch-9.3.0-py3-none-any.whl", hash = "sha256:67bd2bb4f0800f58c2847d29cd57d6e7bf5bc273483b4f17421f93e75ba09f39", size = 979405, upload-time = "2026-02-03T20:26:34.552Z" }, + { url = "https://files.pythonhosted.org/packages/05/37/3a196f8918743f2104cb66b1f56218079ecac6e128c061de7df7f4faef02/elasticsearch-9.3.0-py3-none-any.whl", hash = "sha256:67bd2bb4f0800f58c2847d29cd57d6e7bf5bc273483b4f17421f93e75ba09f39", size = 979405 }, ] [[package]] @@ -1981,18 +1981,18 @@ dependencies = [ { name = "dnspython" }, { name = "idna" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238 } 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" }, + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604 }, ] [[package]] name = "emoji" version = "2.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/78/0d2db9382c92a163d7095fc08efff7800880f830a152cfced40161e7638d/emoji-2.15.0.tar.gz", hash = "sha256:eae4ab7d86456a70a00a985125a03263a5eac54cd55e51d7e184b1ed3b6757e4", size = 615483, upload-time = "2025-09-21T12:13:02.755Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/78/0d2db9382c92a163d7095fc08efff7800880f830a152cfced40161e7638d/emoji-2.15.0.tar.gz", hash = "sha256:eae4ab7d86456a70a00a985125a03263a5eac54cd55e51d7e184b1ed3b6757e4", size = 615483 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/5e/4b5aaaabddfacfe36ba7768817bd1f71a7a810a43705e531f3ae4c690767/emoji-2.15.0-py3-none-any.whl", hash = "sha256:205296793d66a89d88af4688fa57fd6496732eb48917a87175a023c8138995eb", size = 608433, upload-time = "2025-09-21T12:13:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/4b5aaaabddfacfe36ba7768817bd1f71a7a810a43705e531f3ae4c690767/emoji-2.15.0-py3-none-any.whl", hash = "sha256:205296793d66a89d88af4688fa57fd6496732eb48917a87175a023c8138995eb", size = 608433 }, ] [[package]] @@ -2008,30 +2008,30 @@ name = "espeakng-loader" version = "0.2.4" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/92/f44ed7f531143c3c6c97d56e2b0f9be8728dc05e18b96d46eb539230ed46/espeakng_loader-0.2.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b77477ae2ddf62a748e04e49714eabb2f3a24f344166200b00539083bd669904", size = 9938387, upload-time = "2025-01-17T01:22:42.064Z" }, - { url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d27cdca31112226e7299d8562e889d3e38a1e48055c9ee381b45d669072ee59f", size = 9892565, upload-time = "2025-01-17T01:22:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/de/1e/25ec5ab07528c0fbb215a61800a38eca05c8a99445515a02d7fa5debcb32/espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08721baf27d13d461f6be6eed9a65277e70d68234ff484fd8b9897b222cdcb6d", size = 10078484, upload-time = "2025-01-17T01:22:43.373Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ad/1b768d8daffc2996e07bbcb6f534d8de3202cd75fce1f1c45eced1ce6465/espeakng_loader-0.2.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d1e798141b46a050cdb75fcf3c17db969bb2c40394f3f4a48910655d547508b9", size = 10037736, upload-time = "2025-01-17T01:22:42.576Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ed/a3d872fbad4f3a3f3db0e8c31768ab14e77cd77306de16b8b20b1e1df7ea/espeakng_loader-0.2.4-py3-none-win_amd64.whl", hash = "sha256:41f1e08ac9deda2efd1ea9de0b81dab9f5ae3c4b24284f76533d0a7b1dd7abd7", size = 9437292, upload-time = "2025-01-17T01:23:27.463Z" }, - { url = "https://files.pythonhosted.org/packages/29/64/0b75bc50ec53b4e000bac913625511215aa96124adf5dba8c4baa17c02cd/espeakng_loader-0.2.4-py3-none-win_arm64.whl", hash = "sha256:d7a2928843eaeb2df82f99a370f44e8a630f59b02f9b0d1f168a03c4eeb76b89", size = 9426841, upload-time = "2025-01-17T01:23:21.766Z" }, + { url = "https://files.pythonhosted.org/packages/f8/92/f44ed7f531143c3c6c97d56e2b0f9be8728dc05e18b96d46eb539230ed46/espeakng_loader-0.2.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b77477ae2ddf62a748e04e49714eabb2f3a24f344166200b00539083bd669904", size = 9938387 }, + { url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d27cdca31112226e7299d8562e889d3e38a1e48055c9ee381b45d669072ee59f", size = 9892565 }, + { url = "https://files.pythonhosted.org/packages/de/1e/25ec5ab07528c0fbb215a61800a38eca05c8a99445515a02d7fa5debcb32/espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08721baf27d13d461f6be6eed9a65277e70d68234ff484fd8b9897b222cdcb6d", size = 10078484 }, + { url = "https://files.pythonhosted.org/packages/d9/ad/1b768d8daffc2996e07bbcb6f534d8de3202cd75fce1f1c45eced1ce6465/espeakng_loader-0.2.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d1e798141b46a050cdb75fcf3c17db969bb2c40394f3f4a48910655d547508b9", size = 10037736 }, + { url = "https://files.pythonhosted.org/packages/9d/ed/a3d872fbad4f3a3f3db0e8c31768ab14e77cd77306de16b8b20b1e1df7ea/espeakng_loader-0.2.4-py3-none-win_amd64.whl", hash = "sha256:41f1e08ac9deda2efd1ea9de0b81dab9f5ae3c4b24284f76533d0a7b1dd7abd7", size = 9437292 }, + { url = "https://files.pythonhosted.org/packages/29/64/0b75bc50ec53b4e000bac913625511215aa96124adf5dba8c4baa17c02cd/espeakng_loader-0.2.4-py3-none-win_arm64.whl", hash = "sha256:d7a2928843eaeb2df82f99a370f44e8a630f59b02f9b0d1f168a03c4eeb76b89", size = 9426841 }, ] [[package]] name = "et-xmlfile" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, ] [[package]] name = "fake-useragent" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898, upload-time = "2025-04-14T15:32:19.238Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" }, + { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695 }, ] [[package]] @@ -2041,9 +2041,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/e5/b16bf568a2f20fe7423282db4a4059dbcadef70e9029c1c106836f8edd84/faker-40.11.1.tar.gz", hash = "sha256:61965046e79e8cfde4337d243eac04c0d31481a7c010033141103b43f603100c", size = 1957415, upload-time = "2026-03-23T14:05:50.233Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/e5/b16bf568a2f20fe7423282db4a4059dbcadef70e9029c1c106836f8edd84/faker-40.11.1.tar.gz", hash = "sha256:61965046e79e8cfde4337d243eac04c0d31481a7c010033141103b43f603100c", size = 1957415 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/ec/3c4b78eb0d2f6a81fb8cc9286745845bff661e6815741eff7a6ac5fcc9ea/faker-40.11.1-py3-none-any.whl", hash = "sha256:3af3a213ba8fb33ce6ba2af7aef2ac91363dae35d0cec0b2b0337d189e5bee2a", size = 1989484, upload-time = "2026-03-23T14:05:48.793Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ec/3c4b78eb0d2f6a81fb8cc9286745845bff661e6815741eff7a6ac5fcc9ea/faker-40.11.1-py3-none-any.whl", hash = "sha256:3af3a213ba8fb33ce6ba2af7aef2ac91363dae35d0cec0b2b0337d189e5bee2a", size = 1989484 }, ] [[package]] @@ -2057,9 +2057,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833, upload-time = "2026-03-23T14:12:41.697Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407, upload-time = "2026-03-23T14:12:43.284Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407 }, ] [[package]] @@ -2074,9 +2074,9 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/79/98b9d733274cfe0c776fde637dc53b421a0142f51a9e3e9fecd72741f7c1/fastapi_users-15.0.5.tar.gz", hash = "sha256:097f69701894e650c346df89b1cdb0a09cf139234f4cb9a8ece275af4e98e202", size = 121394, upload-time = "2026-03-27T09:01:17.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/79/98b9d733274cfe0c776fde637dc53b421a0142f51a9e3e9fecd72741f7c1/fastapi_users-15.0.5.tar.gz", hash = "sha256:097f69701894e650c346df89b1cdb0a09cf139234f4cb9a8ece275af4e98e202", size = 121394 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/8e/1ed6bbe36c486b98217c4be6769d61fcb98c38b334fb39706de661a905b6/fastapi_users-15.0.5-py3-none-any.whl", hash = "sha256:10fd4f3e85ed66f694a6ca2ecac609af4e59d1f9ec64d1557f5912dccfd87c7f", size = 39038, upload-time = "2026-03-27T09:01:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8e/1ed6bbe36c486b98217c4be6769d61fcb98c38b334fb39706de661a905b6/fastapi_users-15.0.5-py3-none-any.whl", hash = "sha256:10fd4f3e85ed66f694a6ca2ecac609af4e59d1f9ec64d1557f5912dccfd87c7f", size = 39038 }, ] [package.optional-dependencies] @@ -2095,44 +2095,44 @@ dependencies = [ { name = "fastapi-users" }, { name = "sqlalchemy", extra = ["asyncio"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/12/bc9e6146ae31564741cefc87ee6e37fa5b566933f0afe8aa030779d60e60/fastapi_users_db_sqlalchemy-7.0.0.tar.gz", hash = "sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595", size = 10877, upload-time = "2025-01-04T13:09:05.086Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/12/bc9e6146ae31564741cefc87ee6e37fa5b566933f0afe8aa030779d60e60/fastapi_users_db_sqlalchemy-7.0.0.tar.gz", hash = "sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595", size = 10877 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/08/9968963c1fb8c34627b7f1fbcdfe9438540f87dc7c9bfb59bb4fd19a4ecf/fastapi_users_db_sqlalchemy-7.0.0-py3-none-any.whl", hash = "sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e", size = 6891, upload-time = "2025-01-04T13:09:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/a6/08/9968963c1fb8c34627b7f1fbcdfe9438540f87dc7c9bfb59bb4fd19a4ecf/fastapi_users_db_sqlalchemy-7.0.0-py3-none-any.whl", hash = "sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e", size = 6891 }, ] [[package]] name = "fastavro" version = "1.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057, upload-time = "2025-10-10T15:41:24.556Z" }, - { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866, upload-time = "2025-10-10T15:41:26.882Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005, upload-time = "2025-10-10T15:41:29.017Z" }, - { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258, upload-time = "2025-10-10T15:41:31.564Z" }, - { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328, upload-time = "2025-10-10T15:41:33.689Z" }, - { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140, upload-time = "2025-10-10T15:41:34.902Z" }, - { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599, upload-time = "2025-10-10T15:41:36.554Z" }, - { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933, upload-time = "2025-10-10T15:41:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066, upload-time = "2025-10-10T15:41:41.608Z" }, - { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038, upload-time = "2025-10-10T15:41:43.743Z" }, - { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398, upload-time = "2025-10-10T15:41:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220, upload-time = "2025-10-10T15:41:47.39Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611, upload-time = "2025-10-10T15:41:48.818Z" }, - { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001, upload-time = "2025-10-10T15:41:50.871Z" }, - { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217, upload-time = "2025-10-10T15:41:53.149Z" }, - { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742, upload-time = "2025-10-10T15:41:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743, upload-time = "2025-10-10T15:41:57.68Z" }, - { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377, upload-time = "2025-10-10T15:41:59.241Z" }, - { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401, upload-time = "2025-10-10T15:42:01.682Z" }, - { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894, upload-time = "2025-10-10T15:42:04.073Z" }, - { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644, upload-time = "2025-10-10T15:42:06.221Z" }, - { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704, upload-time = "2025-10-10T15:42:08.384Z" }, - { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911, upload-time = "2025-10-10T15:42:09.795Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999, upload-time = "2025-10-10T15:42:11.794Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972, upload-time = "2025-10-10T15:42:14.485Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752, upload-time = "2025-10-10T15:42:16.618Z" }, - { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636, upload-time = "2025-10-10T15:42:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057 }, + { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866 }, + { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005 }, + { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258 }, + { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328 }, + { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140 }, + { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599 }, + { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933 }, + { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066 }, + { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038 }, + { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398 }, + { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220 }, + { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611 }, + { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001 }, + { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217 }, + { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742 }, + { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743 }, + { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377 }, + { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401 }, + { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894 }, + { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644 }, + { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704 }, + { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999 }, + { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972 }, + { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752 }, + { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636 }, ] [[package]] @@ -2148,48 +2148,48 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909, upload-time = "2025-10-31T11:35:47.794Z" }, + { url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909 }, ] [[package]] name = "fastuuid" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, - { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, - { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, - { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, - { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, - { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, - { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, - { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720, upload-time = "2025-10-19T22:42:34.633Z" }, - { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024, upload-time = "2025-10-19T22:30:25.482Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679, upload-time = "2025-10-19T22:36:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862, upload-time = "2025-10-19T22:36:23.302Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278, upload-time = "2025-10-19T22:29:43.809Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788, upload-time = "2025-10-19T22:36:06.825Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819, upload-time = "2025-10-19T22:35:31.623Z" }, - { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546, upload-time = "2025-10-19T22:26:03.023Z" }, - { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921, upload-time = "2025-10-19T22:31:02.151Z" }, - { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559, upload-time = "2025-10-19T22:36:36.011Z" }, - { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539, upload-time = "2025-10-19T22:33:35.898Z" }, - { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600, upload-time = "2025-10-19T22:43:44.17Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069, upload-time = "2025-10-19T22:43:38.38Z" }, - { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543, upload-time = "2025-10-19T22:32:22.537Z" }, - { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798, upload-time = "2025-10-19T22:33:53.821Z" }, - { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283, upload-time = "2025-10-19T22:29:02.812Z" }, - { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627, upload-time = "2025-10-19T22:35:54.985Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778, upload-time = "2025-10-19T22:28:00.999Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605, upload-time = "2025-10-19T22:36:21.764Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837, upload-time = "2025-10-19T22:34:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532, upload-time = "2025-10-19T22:35:18.217Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457, upload-time = "2025-10-19T22:33:44.579Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164 }, + { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837 }, + { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370 }, + { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766 }, + { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105 }, + { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564 }, + { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659 }, + { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430 }, + { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894 }, + { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374 }, + { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550 }, + { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720 }, + { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024 }, + { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679 }, + { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862 }, + { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278 }, + { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788 }, + { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819 }, + { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546 }, + { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921 }, + { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559 }, + { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539 }, + { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600 }, + { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069 }, + { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543 }, + { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798 }, + { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283 }, + { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627 }, + { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778 }, + { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605 }, + { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837 }, + { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532 }, + { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457 }, ] [[package]] @@ -2199,27 +2199,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "future" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543, upload-time = "2019-07-06T00:19:08.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/0c/56be52741f75bad4dc6555991fabd2e07b432d333da82c11ad701123888a/ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5", size = 25024, upload-time = "2019-07-06T00:19:07.215Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0c/56be52741f75bad4dc6555991fabd2e07b432d333da82c11ad701123888a/ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5", size = 25024 }, ] [[package]] name = "filelock" version = "3.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759 }, ] [[package]] name = "filetype" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, ] [[package]] @@ -2235,9 +2235,9 @@ dependencies = [ { name = "requests" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/6b/8201b737c0667bf70748b86a6fb117aefc648154b4e05c5ee649432cbc3d/firecrawl_py-4.21.0.tar.gz", hash = "sha256:14a7e0967d816c711c3c53325c9371e2f780a787d1e94333a34d8aea7a43a237", size = 174256, upload-time = "2026-03-25T16:22:00.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6b/8201b737c0667bf70748b86a6fb117aefc648154b4e05c5ee649432cbc3d/firecrawl_py-4.21.0.tar.gz", hash = "sha256:14a7e0967d816c711c3c53325c9371e2f780a787d1e94333a34d8aea7a43a237", size = 174256 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/f1/1c0f1e5b33a318d7b9705b9e23c4397253d730e516e3d8a2f6aaea4b71a2/firecrawl_py-4.21.0-py3-none-any.whl", hash = "sha256:4e431f36117b4f2aaae633e747859a91626b0f2c6aaa6b7f86dfb7669a3595eb", size = 217607, upload-time = "2026-03-25T16:21:58.708Z" }, + { url = "https://files.pythonhosted.org/packages/18/f1/1c0f1e5b33a318d7b9705b9e23c4397253d730e516e3d8a2f6aaea4b71a2/firecrawl_py-4.21.0-py3-none-any.whl", hash = "sha256:4e431f36117b4f2aaae633e747859a91626b0f2c6aaa6b7f86dfb7669a3595eb", size = 217607 }, ] [[package]] @@ -2251,9 +2251,9 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/1f/176cb4a857a70c3538f637e19389ab6aed21548a1ba1d1424fccc8bba108/FlashRank-0.2.10.tar.gz", hash = "sha256:f8f82a25c32fdfc668a09dc4089421d6aab8e7f71308424b541f40bb3f01d9db", size = 18905, upload-time = "2025-01-06T13:33:01.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/1f/176cb4a857a70c3538f637e19389ab6aed21548a1ba1d1424fccc8bba108/FlashRank-0.2.10.tar.gz", hash = "sha256:f8f82a25c32fdfc668a09dc4089421d6aab8e7f71308424b541f40bb3f01d9db", size = 18905 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/99/72639cc1c9221c5bc77a2df1c2d352fe11965553bdf7d3e0856e7fcc8fd6/FlashRank-0.2.10-py3-none-any.whl", hash = "sha256:5d3272ae657d793c132d1e7917ed9e2adf49e0e1c60735583a67b051c6f0434a", size = 14511, upload-time = "2025-01-06T13:32:59.42Z" }, + { url = "https://files.pythonhosted.org/packages/ec/99/72639cc1c9221c5bc77a2df1c2d352fe11965553bdf7d3e0856e7fcc8fd6/FlashRank-0.2.10-py3-none-any.whl", hash = "sha256:5d3272ae657d793c132d1e7917ed9e2adf49e0e1c60735583a67b051c6f0434a", size = 14511 }, ] [[package]] @@ -2261,7 +2261,7 @@ name = "flatbuffers" version = "25.12.19" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661 }, ] [[package]] @@ -2275,9 +2275,9 @@ dependencies = [ { name = "pytz" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a1/357f1b5d8946deafdcfdd604f51baae9de10aafa2908d0b7322597155f92/flower-2.0.1.tar.gz", hash = "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0", size = 3220408, upload-time = "2023-08-13T14:37:46.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a1/357f1b5d8946deafdcfdd604f51baae9de10aafa2908d0b7322597155f92/flower-2.0.1.tar.gz", hash = "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0", size = 3220408 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ff/ee2f67c0ff146ec98b5df1df637b2bc2d17beeb05df9f427a67bd7a7d79c/flower-2.0.1-py2.py3-none-any.whl", hash = "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2", size = 383553, upload-time = "2023-08-13T14:37:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ff/ee2f67c0ff146ec98b5df1df637b2bc2d17beeb05df9f427a67bd7a7d79c/flower-2.0.1-py2.py3-none-any.whl", hash = "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2", size = 383553 }, ] [[package]] @@ -2287,50 +2287,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/a5/15fe839297d761e04c4578b11013ed46353e63b44b5e42b59c2078602fa1/flupy-1.2.3.tar.gz", hash = "sha256:220b6d40dea238cd2d66784c0d4d2a5483447a48acd343385768e0c740af9609", size = 12327, upload-time = "2025-07-15T14:08:21.14Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a5/15fe839297d761e04c4578b11013ed46353e63b44b5e42b59c2078602fa1/flupy-1.2.3.tar.gz", hash = "sha256:220b6d40dea238cd2d66784c0d4d2a5483447a48acd343385768e0c740af9609", size = 12327 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/26/d4d1629f846ae2913e88f74955a3c3f41f3863e74c5fbc1cb79af9550717/flupy-1.2.3-py3-none-any.whl", hash = "sha256:be0f5a393bad2b3534697fbab17081993cd3f5817169dd3a61e8b2e0887612e6", size = 12512, upload-time = "2025-07-18T20:15:21.384Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/d4d1629f846ae2913e88f74955a3c3f41f3863e74c5fbc1cb79af9550717/flupy-1.2.3-py3-none-any.whl", hash = "sha256:be0f5a393bad2b3534697fbab17081993cd3f5817169dd3a61e8b2e0887612e6", size = 12512 }, ] [[package]] name = "fonttools" version = "4.62.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219 }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891 }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197 }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768 }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512 }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723 }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278 }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414 }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155 }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802 }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926 }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575 }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693 }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920 }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928 }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514 }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442 }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901 }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608 }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726 }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422 }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979 }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733 }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663 }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288 }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023 }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599 }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933 }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232 }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987 }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021 }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147 }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647 }, ] [[package]] @@ -2340,107 +2340,107 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/9e/2c14b6dc812defa58e9d515b44b4d126b48338545e14089413df50dadda3/fractional-indexing-0.1.3.tar.gz", hash = "sha256:59e25f441c213bf7f921aa329a6bf3e5930ee8d8852df0b4e930aaf39a39ac5e", size = 7791, upload-time = "2023-08-13T10:03:16.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/9e/2c14b6dc812defa58e9d515b44b4d126b48338545e14089413df50dadda3/fractional-indexing-0.1.3.tar.gz", hash = "sha256:59e25f441c213bf7f921aa329a6bf3e5930ee8d8852df0b4e930aaf39a39ac5e", size = 7791 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/d1/627b86b1eb95ec2c7e012f0036e28a9335f3177f6c7e15047df6ee544bbc/fractional_indexing-0.1.3-py3-none-any.whl", hash = "sha256:389abb9da24add388af3f0146ee4fcf2ebec5816ee728fa672657e394d9a7824", size = 7971, upload-time = "2023-08-13T10:03:15.504Z" }, + { url = "https://files.pythonhosted.org/packages/02/d1/627b86b1eb95ec2c7e012f0036e28a9335f3177f6c7e15047df6ee544bbc/fractional_indexing-0.1.3-py3-none-any.whl", hash = "sha256:389abb9da24add388af3f0146ee4fcf2ebec5816ee728fa672657e394d9a7824", size = 7971 }, ] [[package]] name = "frozenlist" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, - { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, - { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, - { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, - { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, - { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, - { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, - { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, - { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, - { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, - { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, - { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, - { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, - { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, - { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, - { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782 }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594 }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448 }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411 }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014 }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909 }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049 }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485 }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619 }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320 }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820 }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518 }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096 }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985 }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591 }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102 }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717 }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651 }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417 }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391 }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048 }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549 }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833 }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363 }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314 }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365 }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763 }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110 }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717 }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628 }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882 }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676 }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235 }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742 }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725 }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506 }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161 }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676 }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638 }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067 }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101 }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901 }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395 }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659 }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492 }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034 }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749 }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127 }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698 }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749 }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298 }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015 }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038 }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130 }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845 }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131 }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542 }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308 }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210 }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972 }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536 }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330 }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627 }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238 }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738 }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739 }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186 }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196 }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830 }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289 }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318 }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814 }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762 }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470 }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042 }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148 }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676 }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451 }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507 }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409 }, ] [[package]] name = "fsspec" version = "2025.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491, upload-time = "2025-03-07T21:47:56.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" }, + { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, ] [package.optional-dependencies] @@ -2452,9 +2452,9 @@ http = [ name = "future" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, ] [[package]] @@ -2467,9 +2467,9 @@ dependencies = [ { name = "requests" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/91/603bcaf8cd1b3927de64bf56c3a8915f6653ea7281919140c5bcff2bfe7b/github3.py-4.0.1.tar.gz", hash = "sha256:30d571076753efc389edc7f9aaef338a4fcb24b54d8968d5f39b1342f45ddd36", size = 36214038, upload-time = "2023-04-26T17:56:37.677Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/91/603bcaf8cd1b3927de64bf56c3a8915f6653ea7281919140c5bcff2bfe7b/github3.py-4.0.1.tar.gz", hash = "sha256:30d571076753efc389edc7f9aaef338a4fcb24b54d8968d5f39b1342f45ddd36", size = 36214038 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/2394d4fb542574678b0ba342daf734d4d811768da3c2ee0c84d509dcb26c/github3.py-4.0.1-py3-none-any.whl", hash = "sha256:a89af7de25650612d1da2f0609622bcdeb07ee8a45a1c06b2d16a05e4234e753", size = 151800, upload-time = "2023-04-26T17:56:25.015Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/2394d4fb542574678b0ba342daf734d4d811768da3c2ee0c84d509dcb26c/github3.py-4.0.1-py3-none-any.whl", hash = "sha256:a89af7de25650612d1da2f0609622bcdeb07ee8a45a1c06b2d16a05e4234e753", size = 151800 }, ] [[package]] @@ -2486,9 +2486,9 @@ dependencies = [ { name = "starlette" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/fe/a915f0c32a3d7920206a677f73c185b3eadf4ec151fb05aedd52e64713f7/gitingest-0.3.1.tar.gz", hash = "sha256:4587cab873d4e08bdb16d612bb153c23e0ce59771a1d57a438239c5e39f05ebf", size = 70681, upload-time = "2025-07-31T13:56:19.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/fe/a915f0c32a3d7920206a677f73c185b3eadf4ec151fb05aedd52e64713f7/gitingest-0.3.1.tar.gz", hash = "sha256:4587cab873d4e08bdb16d612bb153c23e0ce59771a1d57a438239c5e39f05ebf", size = 70681 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/15/f200ab2e73287e67d1dce6fbacf421552ae9fbafdc5f0cc8dd0d2fe4fc47/gitingest-0.3.1-py3-none-any.whl", hash = "sha256:8143a5e6a7140ede9f680e13d3931ac07c82ac9bd8bab9ad1fba017c8c1e8666", size = 68343, upload-time = "2025-07-31T13:56:17.729Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/f200ab2e73287e67d1dce6fbacf421552ae9fbafdc5f0cc8dd0d2fe4fc47/gitingest-0.3.1-py3-none-any.whl", hash = "sha256:8143a5e6a7140ede9f680e13d3931ac07c82ac9bd8bab9ad1fba017c8c1e8666", size = 68343 }, ] [[package]] @@ -2502,9 +2502,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/98/586ec94553b569080caef635f98a3723db36a38eac0e3d7eb3ea9d2e4b9a/google_api_core-2.30.0.tar.gz", hash = "sha256:02edfa9fab31e17fc0befb5f161b3bf93c9096d99aed584625f38065c511ad9b", size = 176959, upload-time = "2026-02-18T20:28:11.926Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/98/586ec94553b569080caef635f98a3723db36a38eac0e3d7eb3ea9d2e4b9a/google_api_core-2.30.0.tar.gz", hash = "sha256:02edfa9fab31e17fc0befb5f161b3bf93c9096d99aed584625f38065c511ad9b", size = 176959 } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl", hash = "sha256:80be49ee937ff9aba0fd79a6eddfde35fe658b9953ab9b79c57dd7061afa8df5", size = 173288, upload-time = "2026-02-18T20:28:10.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl", hash = "sha256:80be49ee937ff9aba0fd79a6eddfde35fe658b9953ab9b79c57dd7061afa8df5", size = 173288 }, ] [package.optional-dependencies] @@ -2524,9 +2524,9 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/f4/e14b6815d3b1885328dd209676a3a4c704882743ac94e18ef0093894f5c8/google_api_python_client-2.193.0.tar.gz", hash = "sha256:8f88d16e89d11341e0a8b199cafde0fb7e6b44260dffb88d451577cbd1bb5d33", size = 14281006, upload-time = "2026-03-17T18:25:29.415Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/f4/e14b6815d3b1885328dd209676a3a4c704882743ac94e18ef0093894f5c8/google_api_python_client-2.193.0.tar.gz", hash = "sha256:8f88d16e89d11341e0a8b199cafde0fb7e6b44260dffb88d451577cbd1bb5d33", size = 14281006 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/6d/fe75167797790a56d17799b75e1129bb93f7ff061efc7b36e9731bd4be2b/google_api_python_client-2.193.0-py3-none-any.whl", hash = "sha256:c42aa324b822109901cfecab5dc4fc3915d35a7b376835233c916c70610322db", size = 14856490, upload-time = "2026-03-17T18:25:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6d/fe75167797790a56d17799b75e1129bb93f7ff061efc7b36e9731bd4be2b/google_api_python_client-2.193.0-py3-none-any.whl", hash = "sha256:c42aa324b822109901cfecab5dc4fc3915d35a7b376835233c916c70610322db", size = 14856490 }, ] [[package]] @@ -2537,9 +2537,9 @@ dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825, upload-time = "2026-03-12T19:30:58.135Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737, upload-time = "2026-03-12T19:30:53.159Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737 }, ] [package.optional-dependencies] @@ -2555,9 +2555,9 @@ dependencies = [ { name = "google-auth" }, { name = "httplib2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134, upload-time = "2025-12-15T22:13:51.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529, upload-time = "2025-12-15T22:13:51.048Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529 }, ] [[package]] @@ -2568,9 +2568,9 @@ dependencies = [ { name = "google-auth" }, { name = "requests-oauthlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b4/1b19567e4c567b796f5c593d89895f3cfae5a38e04f27c6af87618fd0942/google_auth_oauthlib-1.3.0.tar.gz", hash = "sha256:cd39e807ac7229d6b8b9c1e297321d36fcc8a9e4857dff4301870985df51a528", size = 21777, upload-time = "2026-02-27T14:13:01.489Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b4/1b19567e4c567b796f5c593d89895f3cfae5a38e04f27c6af87618fd0942/google_auth_oauthlib-1.3.0.tar.gz", hash = "sha256:cd39e807ac7229d6b8b9c1e297321d36fcc8a9e4857dff4301870985df51a528", size = 21777 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl", hash = "sha256:386b3fb85cf4a5b819c6ad23e3128d975216b4cac76324de1d90b128aaf38f29", size = 19308, upload-time = "2026-02-27T14:12:47.865Z" }, + { url = "https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl", hash = "sha256:386b3fb85cf4a5b819c6ad23e3128d975216b4cac76324de1d90b128aaf38f29", size = 19308 }, ] [[package]] @@ -2584,9 +2584,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/f9/208ae25a03f822fcc7f762198cdedaefdbac4f923f72e5c39d3bdbf2ec60/google_cloud_vision-3.13.0.tar.gz", hash = "sha256:680f668d331858a3340eac41b732903d30dc69ed08020ffd1d5ca32580bdf546", size = 592075, upload-time = "2026-03-26T22:18:38.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/f9/208ae25a03f822fcc7f762198cdedaefdbac4f923f72e5c39d3bdbf2ec60/google_cloud_vision-3.13.0.tar.gz", hash = "sha256:680f668d331858a3340eac41b732903d30dc69ed08020ffd1d5ca32580bdf546", size = 592075 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/74/775192dc2a930191e821c5cd841d399576ae7bca4db98ee5cc262ac56de0/google_cloud_vision-3.13.0-py3-none-any.whl", hash = "sha256:f6979e93ad60a7e556b152de2857f7d3b9b740afd022cea1c76548ef80c29b87", size = 543152, upload-time = "2026-03-26T22:13:13.127Z" }, + { url = "https://files.pythonhosted.org/packages/c8/74/775192dc2a930191e821c5cd841d399576ae7bca4db98ee5cc262ac56de0/google_cloud_vision-3.13.0-py3-none-any.whl", hash = "sha256:f6979e93ad60a7e556b152de2857f7d3b9b740afd022cea1c76548ef80c29b87", size = 543152 }, ] [[package]] @@ -2605,9 +2605,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/2c/f059982dbcb658cc535c81bbcbe7e2c040d675f4b563b03cdb01018a4bc3/google_genai-1.68.0.tar.gz", hash = "sha256:ac30c0b8bc630f9372993a97e4a11dae0e36f2e10d7c55eacdca95a9fa14ca96", size = 511285, upload-time = "2026-03-18T01:03:18.243Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/2c/f059982dbcb658cc535c81bbcbe7e2c040d675f4b563b03cdb01018a4bc3/google_genai-1.68.0.tar.gz", hash = "sha256:ac30c0b8bc630f9372993a97e4a11dae0e36f2e10d7c55eacdca95a9fa14ca96", size = 511285 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/de/7d3ee9c94b74c3578ea4f88d45e8de9405902f857932334d81e89bce3dfa/google_genai-1.68.0-py3-none-any.whl", hash = "sha256:a1bc9919c0e2ea2907d1e319b65471d3d6d58c54822039a249fe1323e4178d15", size = 750912, upload-time = "2026-03-18T01:03:15.983Z" }, + { url = "https://files.pythonhosted.org/packages/84/de/7d3ee9c94b74c3578ea4f88d45e8de9405902f857932334d81e89bce3dfa/google_genai-1.68.0-py3-none-any.whl", hash = "sha256:a1bc9919c0e2ea2907d1e319b65471d3d6d58c54822039a249fe1323e4178d15", size = 750912 }, ] [[package]] @@ -2617,52 +2617,52 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506, upload-time = "2026-03-26T22:17:38.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556, upload-time = "2026-03-26T22:15:58.455Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556 }, ] [[package]] name = "greenlet" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, - { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, - { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, - { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, - { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, - { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358 }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217 }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792 }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250 }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875 }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467 }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001 }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081 }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331 }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120 }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238 }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219 }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268 }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774 }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277 }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455 }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961 }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221 }, + { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650 }, + { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295 }, + { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163 }, + { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371 }, + { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160 }, + { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181 }, + { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713 }, + { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034 }, + { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437 }, + { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617 }, + { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189 }, + { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225 }, + { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581 }, + { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907 }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857 }, + { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010 }, + { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086 }, ] [[package]] @@ -2673,9 +2673,9 @@ dependencies = [ { name = "griffecli" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4a/49/eb6d2935e27883af92c930ed40cc4c69bcd32c402be43b8ca4ab20510f67/griffe-2.0.2.tar.gz", hash = "sha256:c5d56326d159f274492e9bf93a9895cec101155d944caa66d0fc4e0c13751b92", size = 293757, upload-time = "2026-03-27T11:34:52.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/49/eb6d2935e27883af92c930ed40cc4c69bcd32c402be43b8ca4ab20510f67/griffe-2.0.2.tar.gz", hash = "sha256:c5d56326d159f274492e9bf93a9895cec101155d944caa66d0fc4e0c13751b92", size = 293757 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/c0/2bb018eecf9a83c68db9cd9fffd9dab25f102ad30ed869451046e46d1187/griffe-2.0.2-py3-none-any.whl", hash = "sha256:2b31816460aee1996af26050a1fc6927a2e5936486856707f55508e4c9b5960b", size = 5141, upload-time = "2026-03-27T11:34:47.721Z" }, + { url = "https://files.pythonhosted.org/packages/94/c0/2bb018eecf9a83c68db9cd9fffd9dab25f102ad30ed869451046e46d1187/griffe-2.0.2-py3-none-any.whl", hash = "sha256:2b31816460aee1996af26050a1fc6927a2e5936486856707f55508e4c9b5960b", size = 5141 }, ] [[package]] @@ -2686,18 +2686,18 @@ dependencies = [ { name = "colorama" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/e0/6a7d661d71bb043656a109b91d84a42b5342752542074ec83b16a6eb97f0/griffecli-2.0.2.tar.gz", hash = "sha256:40a1ad4181fc39685d025e119ae2c5b669acdc1f19b705fb9bf971f4e6f6dffb", size = 56281, upload-time = "2026-03-27T11:34:50.087Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/e0/6a7d661d71bb043656a109b91d84a42b5342752542074ec83b16a6eb97f0/griffecli-2.0.2.tar.gz", hash = "sha256:40a1ad4181fc39685d025e119ae2c5b669acdc1f19b705fb9bf971f4e6f6dffb", size = 56281 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/e8/90d93356c88ac34c20cb5edffca68138df55ca9bbd1a06eccfbcec8fdbe5/griffecli-2.0.2-py3-none-any.whl", hash = "sha256:0d44d39e59afa81e288a3e1c3bf352cc4fa537483326ac06b8bb6a51fd8303a0", size = 9500, upload-time = "2026-03-27T11:34:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e8/90d93356c88ac34c20cb5edffca68138df55ca9bbd1a06eccfbcec8fdbe5/griffecli-2.0.2-py3-none-any.whl", hash = "sha256:0d44d39e59afa81e288a3e1c3bf352cc4fa537483326ac06b8bb6a51fd8303a0", size = 9500 }, ] [[package]] name = "griffelib" version = "2.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357 }, ] [[package]] @@ -2712,9 +2712,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/c7/a2153b639062f59f9bc93a1b5507c0c4a6b654b8a9edbf432ec2f4a62d2d/groq-1.1.2.tar.gz", hash = "sha256:9ec2b5b6a1c4856a8c6c38741353c5ab37472a4e3fded02af783750d849cc988", size = 154033, upload-time = "2026-03-25T23:16:10.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/c7/a2153b639062f59f9bc93a1b5507c0c4a6b654b8a9edbf432ec2f4a62d2d/groq-1.1.2.tar.gz", hash = "sha256:9ec2b5b6a1c4856a8c6c38741353c5ab37472a4e3fded02af783750d849cc988", size = 154033 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/b0/83e3892a4597a4b8ebf8a662aeaf314765c4c2340516eb1d049b459b24fc/groq-1.1.2-py3-none-any.whl", hash = "sha256:348cb7a674b6aa7105719b533f6fc48fd32b503bc9256924aaed6dc186f778b5", size = 141700, upload-time = "2026-03-25T23:16:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/34/b0/83e3892a4597a4b8ebf8a662aeaf314765c4c2340516eb1d049b459b24fc/groq-1.1.2-py3-none-any.whl", hash = "sha256:348cb7a674b6aa7105719b533f6fc48fd32b503bc9256924aaed6dc186f778b5", size = 141700 }, ] [[package]] @@ -2724,38 +2724,38 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, - { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, - { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, - { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, - { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, - { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, - { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, - { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, - { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985 }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853 }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766 }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027 }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766 }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161 }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303 }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222 }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123 }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657 }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143 }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926 }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628 }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574 }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639 }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838 }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878 }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412 }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899 }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393 }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591 }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803 }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206 }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826 }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897 }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404 }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837 }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439 }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852 }, ] [[package]] @@ -2767,18 +2767,18 @@ dependencies = [ { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808, upload-time = "2026-02-06T10:01:48.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523, upload-time = "2026-02-06T10:01:32.584Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523 }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] [[package]] @@ -2789,50 +2789,50 @@ dependencies = [ { name = "hpack" }, { name = "hyperframe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779 }, ] [[package]] name = "hf-xet" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357, upload-time = "2026-03-13T06:58:51.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125, upload-time = "2026-03-13T06:58:33.177Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985, upload-time = "2026-03-13T06:58:31.797Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085, upload-time = "2026-03-13T06:58:24.323Z" }, - { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266, upload-time = "2026-03-13T06:58:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513, upload-time = "2026-03-13T06:58:40.858Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287, upload-time = "2026-03-13T06:58:42.601Z" }, - { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574, upload-time = "2026-03-13T06:58:53.881Z" }, - { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760, upload-time = "2026-03-13T06:58:52.187Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493, upload-time = "2026-03-13T06:58:39.267Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797, upload-time = "2026-03-13T06:58:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127, upload-time = "2026-03-13T06:58:30.539Z" }, - { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788, upload-time = "2026-03-13T06:58:29.139Z" }, - { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315, upload-time = "2026-03-13T06:58:48.017Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306, upload-time = "2026-03-13T06:58:49.502Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826, upload-time = "2026-03-13T06:58:59.88Z" }, - { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113, upload-time = "2026-03-13T06:58:58.491Z" }, - { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339, upload-time = "2026-03-13T06:58:36.245Z" }, - { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664, upload-time = "2026-03-13T06:58:34.787Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422, upload-time = "2026-03-13T06:58:27.472Z" }, - { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847, upload-time = "2026-03-13T06:58:25.989Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843, upload-time = "2026-03-13T06:58:44.59Z" }, - { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751, upload-time = "2026-03-13T06:58:46.533Z" }, - { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149, upload-time = "2026-03-13T06:58:57.07Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426, upload-time = "2026-03-13T06:58:55.46Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125 }, + { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985 }, + { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085 }, + { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266 }, + { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513 }, + { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287 }, + { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574 }, + { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760 }, + { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493 }, + { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127 }, + { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788 }, + { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315 }, + { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306 }, + { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826 }, + { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113 }, + { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339 }, + { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664 }, + { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422 }, + { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847 }, + { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843 }, + { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751 }, + { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149 }, + { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426 }, ] [[package]] name = "hpack" version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, ] [[package]] @@ -2843,9 +2843,9 @@ dependencies = [ { name = "six" }, { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, ] [[package]] @@ -2859,9 +2859,9 @@ dependencies = [ { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/10/ead9dabc999f353c3aa5d0dc0835b1e355215a5ecb489a7f4ef2ddad5e33/htmldate-1.9.4.tar.gz", hash = "sha256:1129063e02dd0354b74264de71e950c0c3fcee191178321418ccad2074cc8ed0", size = 44690, upload-time = "2025-11-04T17:46:44.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/10/ead9dabc999f353c3aa5d0dc0835b1e355215a5ecb489a7f4ef2ddad5e33/htmldate-1.9.4.tar.gz", hash = "sha256:1129063e02dd0354b74264de71e950c0c3fcee191178321418ccad2074cc8ed0", size = 44690 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/bd/adfcdaaad5805c0c5156aeefd64c1e868c05e9c1cd6fd21751f168cd88c7/htmldate-1.9.4-py3-none-any.whl", hash = "sha256:1b94bcc4e08232a5b692159903acf95548b6a7492dddca5bb123d89d6325921c", size = 31558, upload-time = "2025-11-04T17:46:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/a1/bd/adfcdaaad5805c0c5156aeefd64c1e868c05e9c1cd6fd21751f168cd88c7/htmldate-1.9.4-py3-none-any.whl", hash = "sha256:1b94bcc4e08232a5b692159903acf95548b6a7492dddca5bb123d89d6325921c", size = 31558 }, ] [[package]] @@ -2872,9 +2872,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } 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" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] [[package]] @@ -2884,38 +2884,38 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099 }, ] [[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" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961 } wheels = [ - { 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/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205 }, ] [[package]] @@ -2928,9 +2928,9 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] [package.optional-dependencies] @@ -2945,18 +2945,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/07/db4ad128da3926be22eec586aa87dafd8840c9eb03fe88505fbed016b5c6/httpx_oauth-0.16.1.tar.gz", hash = "sha256:7402f061f860abc092ea4f5c90acfc576a40bbb79633c1d2920f1ca282c296ee", size = 44148, upload-time = "2024-12-20T07:23:02.589Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/07/db4ad128da3926be22eec586aa87dafd8840c9eb03fe88505fbed016b5c6/httpx_oauth-0.16.1.tar.gz", hash = "sha256:7402f061f860abc092ea4f5c90acfc576a40bbb79633c1d2920f1ca282c296ee", size = 44148 } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/4b/2b81e876abf77b4af3372aff731f4f6722840ebc7dcfd85778eaba271733/httpx_oauth-0.16.1-py3-none-any.whl", hash = "sha256:2fcad82f80f28d0473a0fc4b4eda223dc952050af7e3a8c8781342d850f09fb5", size = 38056, upload-time = "2024-12-20T07:23:00.394Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/2b81e876abf77b4af3372aff731f4f6722840ebc7dcfd85778eaba271733/httpx_oauth-0.16.1-py3-none-any.whl", hash = "sha256:2fcad82f80f28d0473a0fc4b4eda223dc952050af7e3a8c8781342d850f09fb5", size = 38056 }, ] [[package]] name = "httpx-sse" version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960 }, ] [[package]] @@ -2973,27 +2973,27 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395 }, ] [[package]] name = "humanize" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203 }, ] [[package]] name = "hyperframe" version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, ] [[package]] @@ -3003,18 +3003,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689 }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, ] [[package]] @@ -3024,45 +3024,45 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, ] [[package]] name = "importlib-resources" version = "6.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, ] [[package]] name = "installer" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/18/ceeb4e3ab3aa54495775775b38ae42b10a92f42ce42dfa44da684289b8c8/installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631", size = 474349, upload-time = "2023-03-17T20:39:38.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/18/ceeb4e3ab3aa54495775775b38ae42b10a92f42ce42dfa44da684289b8c8/installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631", size = 474349 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", size = 453838, upload-time = "2023-03-17T20:39:36.219Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", size = 453838 }, ] [[package]] name = "isodate" version = "0.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 }, ] [[package]] @@ -3072,18 +3072,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, ] [[package]] name = "jaraco-context" version = "6.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871 }, ] [[package]] @@ -3093,18 +3093,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481 }, ] [[package]] name = "jeepney" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } 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" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, ] [[package]] @@ -3114,95 +3114,91 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } 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" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] name = "jiter" version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, - { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, - { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, - { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, - { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, - { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, - { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, - { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, - { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, - { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, - { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, - { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, - { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, - { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, - { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, - { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, - { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, - { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, - { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, - { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, - { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, - { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, - { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, - { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, - { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, - { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958 }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597 }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821 }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163 }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709 }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480 }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735 }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814 }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990 }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021 }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024 }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424 }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818 }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897 }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507 }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560 }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232 }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727 }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799 }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120 }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664 }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543 }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262 }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630 }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602 }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939 }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616 }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850 }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551 }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950 }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852 }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804 }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787 }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880 }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702 }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319 }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289 }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165 }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634 }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933 }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842 }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108 }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027 }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199 }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438 }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774 }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238 }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892 }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607 }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986 }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756 }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196 }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215 }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152 }, ] [[package]] name = "jmespath" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419 }, ] [[package]] name = "joblib" version = "1.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071 }, ] [[package]] @@ -3212,9 +3208,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/d8/423895b918706c80db1cee679c13fbe810200b9a9d9a9442c7a58d35c3f2/json_schema_to_pydantic-0.4.11.tar.gz", hash = "sha256:35448ed711a28dd33396b095c8492939b4925aa30eb31942e9b8e08d04279465", size = 56597, upload-time = "2026-03-09T20:53:55.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/d8/423895b918706c80db1cee679c13fbe810200b9a9d9a9442c7a58d35c3f2/json_schema_to_pydantic-0.4.11.tar.gz", hash = "sha256:35448ed711a28dd33396b095c8492939b4925aa30eb31942e9b8e08d04279465", size = 56597 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/64/7cfeb8c6d2a5e73e0f8d732032aa62be9a7724c04beb461d677de0b4beb3/json_schema_to_pydantic-0.4.11-py3-none-any.whl", hash = "sha256:da2ccc39d070ee03dbcf0517d16720e3e33f7aa8d61257ace09af8c51bd46348", size = 17842, upload-time = "2026-03-09T20:53:54.576Z" }, + { url = "https://files.pythonhosted.org/packages/f3/64/7cfeb8c6d2a5e73e0f8d732032aa62be9a7724c04beb461d677de0b4beb3/json_schema_to_pydantic-0.4.11-py3-none-any.whl", hash = "sha256:da2ccc39d070ee03dbcf0517d16720e3e33f7aa8d61257ace09af8c51bd46348", size = 17842 }, ] [[package]] @@ -3224,9 +3220,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359, upload-time = "2023-09-01T12:34:44.187Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701, upload-time = "2023-09-01T12:34:42.563Z" }, + { url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701 }, ] [[package]] @@ -3236,27 +3232,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpointer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 }, ] [[package]] name = "jsonpointer" version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659 }, ] [[package]] name = "jsonref" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425 }, ] [[package]] @@ -3269,9 +3265,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, ] [[package]] @@ -3281,9 +3277,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } 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" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, ] [[package]] @@ -3293,9 +3289,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml", extra = ["html-clean"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/f3/45890c1b314f0d04e19c1c83d534e611513150939a7cf039664d9ab1e649/justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05", size = 828521, upload-time = "2025-02-25T20:21:49.934Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/f3/45890c1b314f0d04e19c1c83d534e611513150939a7cf039664d9ab1e649/justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05", size = 828521 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940, upload-time = "2025-02-25T20:21:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940 }, ] [[package]] @@ -3310,95 +3306,91 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160 }, ] [[package]] name = "kiwisolver" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, - { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, - { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, - { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, - { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, - { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, - { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, - { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, - { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, - { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, - { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, - { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, - { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, - { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, - { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, - { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, - { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, - { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, - { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, - { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, - { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, - { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, - { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, - { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, - { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, - { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, - { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, - { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, - { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, - { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, - { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, - { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, - { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, - { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, - { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, - { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, - { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, - { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, - { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158 }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388 }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068 }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934 }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537 }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685 }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024 }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241 }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742 }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966 }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417 }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238 }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947 }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569 }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997 }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166 }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395 }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065 }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903 }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751 }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793 }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041 }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292 }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865 }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369 }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989 }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645 }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237 }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573 }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998 }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700 }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537 }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514 }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848 }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542 }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447 }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918 }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856 }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580 }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018 }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804 }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482 }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328 }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231 }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489 }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063 }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913 }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782 }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815 }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925 }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322 }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857 }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376 }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680 }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905 }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086 }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577 }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794 }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646 }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511 }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858 }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539 }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310 }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244 }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154 }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377 }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288 }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158 }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260 }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403 }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687 }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032 }, ] [[package]] @@ -3413,9 +3405,9 @@ dependencies = [ { name = "torch" }, { name = "transformers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/48/88b8cdf28b068d070195c2817175549dee48e7682e3ab8994bee5f69217e/kokoro-0.9.4.tar.gz", hash = "sha256:fbf633262797f8cf46fdac3315cf9cade67dc8b762c0feccf334892772fb9ac4", size = 26215928, upload-time = "2025-04-05T22:01:35.294Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/48/88b8cdf28b068d070195c2817175549dee48e7682e3ab8994bee5f69217e/kokoro-0.9.4.tar.gz", hash = "sha256:fbf633262797f8cf46fdac3315cf9cade67dc8b762c0feccf334892772fb9ac4", size = 26215928 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/cc/75f41633c75224ba820a4533163bc8b070b6bf25416014074c63284c2d4e/kokoro-0.9.4-py3-none-any.whl", hash = "sha256:a129dc6364a286bd6a92c396e9862459d3d3e45f2c15596ed5a94dcee5789efd", size = 32592, upload-time = "2025-04-05T22:01:23.018Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/75f41633c75224ba820a4533163bc8b070b6bf25416014074c63284c2d4e/kokoro-0.9.4-py3-none-any.whl", hash = "sha256:a129dc6364a286bd6a92c396e9862459d3d3e45f2c15596ed5a94dcee5789efd", size = 32592 }, ] [[package]] @@ -3428,9 +3420,9 @@ dependencies = [ { name = "tzdata" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/a5/607e533ed6c83ae1a696969b8e1c137dfebd5759a2e9682e26ff1b97740b/kombu-5.6.2.tar.gz", hash = "sha256:8060497058066c6f5aed7c26d7cd0d3b574990b09de842a8c5aaed0b92cc5a55", size = 472594, upload-time = "2025-12-29T20:30:07.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/a5/607e533ed6c83ae1a696969b8e1c137dfebd5759a2e9682e26ff1b97740b/kombu-5.6.2.tar.gz", hash = "sha256:8060497058066c6f5aed7c26d7cd0d3b574990b09de842a8c5aaed0b92cc5a55", size = 472594 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/0f/834427d8c03ff1d7e867d3db3d176470c64871753252b21b4f4897d1fa45/kombu-5.6.2-py3-none-any.whl", hash = "sha256:efcfc559da324d41d61ca311b0c64965ea35b4c55cc04ee36e55386145dace93", size = 214219, upload-time = "2025-12-29T20:30:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0f/834427d8c03ff1d7e867d3db3d176470c64871753252b21b4f4897d1fa45/kombu-5.6.2-py3-none-any.whl", hash = "sha256:efcfc559da324d41d61ca311b0c64965ea35b4c55cc04ee36e55386145dace93", size = 214219 }, ] [package.optional-dependencies] @@ -3453,9 +3445,9 @@ dependencies = [ { name = "urllib3" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642, upload-time = "2026-01-16T01:05:27.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602, upload-time = "2026-01-16T01:05:25.991Z" }, + { url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602 }, ] [[package]] @@ -3467,9 +3459,9 @@ dependencies = [ { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/e5/56fdeedaa0ef1be3c53721d382d9e21c63930179567361610ea6102c04ea/langchain-1.2.13.tar.gz", hash = "sha256:d566ef67c8287e7f2e2df3c99bf3953a6beefd2a75a97fe56ecce905e21f3ef4", size = 573819, upload-time = "2026-03-19T17:16:07.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e5/56fdeedaa0ef1be3c53721d382d9e21c63930179567361610ea6102c04ea/langchain-1.2.13.tar.gz", hash = "sha256:d566ef67c8287e7f2e2df3c99bf3953a6beefd2a75a97fe56ecce905e21f3ef4", size = 573819 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/1d/a509af07535d8f4621d77f3ba5ec846ee6d52c59d2239e1385ec3b29bf92/langchain-1.2.13-py3-none-any.whl", hash = "sha256:37d4526ac4b0cdd3d7706a6366124c30dc0771bf5340865b37cdc99d5e5ad9b1", size = 112488, upload-time = "2026-03-19T17:16:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1d/a509af07535d8f4621d77f3ba5ec846ee6d52c59d2239e1385ec3b29bf92/langchain-1.2.13-py3-none-any.whl", hash = "sha256:37d4526ac4b0cdd3d7706a6366124c30dc0771bf5340865b37cdc99d5e5ad9b1", size = 112488 }, ] [[package]] @@ -3481,9 +3473,9 @@ dependencies = [ { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/c7/259d4d805c6ac90c8695714fc15498a4557bb515eb24f692fd611966e383/langchain_anthropic-1.4.0.tar.gz", hash = "sha256:bbf64e99f9149a34ba67813e9582b2160a0968de9e9f54f7ba8d1658f253c2e5", size = 674360, upload-time = "2026-03-17T18:42:20.751Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c7/259d4d805c6ac90c8695714fc15498a4557bb515eb24f692fd611966e383/langchain_anthropic-1.4.0.tar.gz", hash = "sha256:bbf64e99f9149a34ba67813e9582b2160a0968de9e9f54f7ba8d1658f253c2e5", size = 674360 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/c0/77f99373276d4f06c38a887ef6023f101cfc7ba3b2bf9af37064cdbadde5/langchain_anthropic-1.4.0-py3-none-any.whl", hash = "sha256:c84f55722336935f7574d5771598e674f3959fdca0b51de14c9788dbf52761be", size = 48463, upload-time = "2026-03-17T18:42:19.742Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c0/77f99373276d4f06c38a887ef6023f101cfc7ba3b2bf9af37064cdbadde5/langchain_anthropic-1.4.0-py3-none-any.whl", hash = "sha256:c84f55722336935f7574d5771598e674f3959fdca0b51de14c9788dbf52761be", size = 48463 }, ] [[package]] @@ -3499,9 +3491,9 @@ dependencies = [ { name = "requests" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/04/b01c09e37414bab9f209efa311502841a3c0de5bc6c35e729c8d8a9893c9/langchain_classic-1.0.3.tar.gz", hash = "sha256:168ef1dfbfb18cae5a9ff0accecc9413a5b5aa3464b53fa841561a3384b6324a", size = 10534933, upload-time = "2026-03-13T13:56:11.96Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/04/b01c09e37414bab9f209efa311502841a3c0de5bc6c35e729c8d8a9893c9/langchain_classic-1.0.3.tar.gz", hash = "sha256:168ef1dfbfb18cae5a9ff0accecc9413a5b5aa3464b53fa841561a3384b6324a", size = 10534933 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/e6/cfdeedec0537ffbf5041773590d25beb7f2aa467cc6630e788c9c7c72c3e/langchain_classic-1.0.3-py3-none-any.whl", hash = "sha256:26df1ec9806b1fbff19d9085a747ea7d8d82d7e3fb1d25132859979de627ef79", size = 1041335, upload-time = "2026-03-13T13:56:09.677Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e6/cfdeedec0537ffbf5041773590d25beb7f2aa467cc6630e788c9c7c72c3e/langchain_classic-1.0.3-py3-none-any.whl", hash = "sha256:26df1ec9806b1fbff19d9085a747ea7d8d82d7e3fb1d25132859979de627ef79", size = 1041335 }, ] [[package]] @@ -3522,9 +3514,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144, upload-time = "2025-10-27T15:20:32.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/a4/c4fde67f193401512337456cabc2148f2c43316e445f5decd9f8806e2992/langchain_community-0.4.1-py3-none-any.whl", hash = "sha256:2135abb2c7748a35c84613108f7ebf30f8505b18c3c18305ffaecfc7651f6c6a", size = 2533285, upload-time = "2025-10-27T15:20:30.767Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a4/c4fde67f193401512337456cabc2148f2c43316e445f5decd9f8806e2992/langchain_community-0.4.1-py3-none-any.whl", hash = "sha256:2135abb2c7748a35c84613108f7ebf30f8505b18c3c18305ffaecfc7651f6c6a", size = 2533285 }, ] [[package]] @@ -3541,9 +3533,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487, upload-time = "2026-03-24T18:48:44.9Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829, upload-time = "2026-03-24T18:48:43.286Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829 }, ] [[package]] @@ -3554,9 +3546,9 @@ dependencies = [ { name = "daytona" }, { name = "deepagents" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/ff/5a4a7ef32f1697c9084c0c908e268f3715b9e1c4963984035737b1ff139a/langchain_daytona-0.0.4.tar.gz", hash = "sha256:037a88cbba4a198d2832303c7f89d5df082e82bad67d8f8eba4a954d6eea3a05", size = 187188, upload-time = "2026-03-11T17:24:11.332Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ff/5a4a7ef32f1697c9084c0c908e268f3715b9e1c4963984035737b1ff139a/langchain_daytona-0.0.4.tar.gz", hash = "sha256:037a88cbba4a198d2832303c7f89d5df082e82bad67d8f8eba4a954d6eea3a05", size = 187188 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/ce/40298b8839a51f18892a07839fb22e19ad99847a742ea5b43eab1f779033/langchain_daytona-0.0.4-py3-none-any.whl", hash = "sha256:ea50516c03417bd365ba362885e473a43627ea2443eddd304bf8d46868275720", size = 5104, upload-time = "2026-03-11T17:24:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ce/40298b8839a51f18892a07839fb22e19ad99847a742ea5b43eab1f779033/langchain_daytona-0.0.4-py3-none-any.whl", hash = "sha256:ea50516c03417bd365ba362885e473a43627ea2443eddd304bf8d46868275720", size = 5104 }, ] [[package]] @@ -3569,9 +3561,9 @@ dependencies = [ { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/63/e7d148f903cebfef50109da71378f411166f068d66f79b9e16a62dbacf41/langchain_google_genai-4.2.1.tar.gz", hash = "sha256:7f44487a0337535897e3bba9a1d6605d722629e034f757ffa8755af0aa85daa8", size = 278288, upload-time = "2026-02-19T19:29:19.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/63/e7d148f903cebfef50109da71378f411166f068d66f79b9e16a62dbacf41/langchain_google_genai-4.2.1.tar.gz", hash = "sha256:7f44487a0337535897e3bba9a1d6605d722629e034f757ffa8755af0aa85daa8", size = 278288 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/7e/46c5973bd8b10a5c4c8a77136cf536e658796380a17c740246074901b038/langchain_google_genai-4.2.1-py3-none-any.whl", hash = "sha256:a7735289cf94ca3a684d830e09196aac8f6e75e647e3a0a1c3c9dc534ceb985e", size = 66500, upload-time = "2026-02-19T19:29:18.002Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7e/46c5973bd8b10a5c4c8a77136cf536e658796380a17c740246074901b038/langchain_google_genai-4.2.1-py3-none-any.whl", hash = "sha256:a7735289cf94ca3a684d830e09196aac8f6e75e647e3a0a1c3c9dc534ceb985e", size = 66500 }, ] [[package]] @@ -3584,9 +3576,9 @@ dependencies = [ { name = "langchain-core" }, { name = "litellm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/37/ccc1f284a42900ca5b267a50da8e50145e9f264b32ee955ce91aa360d188/langchain_litellm-0.6.4.tar.gz", hash = "sha256:663281db392b3de1f07f891d0f80f9d4b26c0f0d2abbf854ef9b186d99c309ee", size = 339457, upload-time = "2026-04-03T16:56:47.886Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/37/ccc1f284a42900ca5b267a50da8e50145e9f264b32ee955ce91aa360d188/langchain_litellm-0.6.4.tar.gz", hash = "sha256:663281db392b3de1f07f891d0f80f9d4b26c0f0d2abbf854ef9b186d99c309ee", size = 339457 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e8/25c50bbad7a05106c7af65557e165d6cb6159c90854dae61de59debe735d/langchain_litellm-0.6.4-py3-none-any.whl", hash = "sha256:60f4e37be1a47dc88f94fac7085675ef8fa04bba92f48735792d82f492120744", size = 26360, upload-time = "2026-04-03T16:56:46.76Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/25c50bbad7a05106c7af65557e165d6cb6159c90854dae61de59debe735d/langchain_litellm-0.6.4-py3-none-any.whl", hash = "sha256:60f4e37be1a47dc88f94fac7085675ef8fa04bba92f48735792d82f492120744", size = 26360 }, ] [[package]] @@ -3596,9 +3588,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/38/14121ead61e0e75f79c3a35e5148ac7c2fe754a55f76eab3eed573269524/langchain_text_splitters-1.1.1.tar.gz", hash = "sha256:34861abe7c07d9e49d4dc852d0129e26b32738b60a74486853ec9b6d6a8e01d2", size = 279352, upload-time = "2026-02-18T23:02:42.798Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/38/14121ead61e0e75f79c3a35e5148ac7c2fe754a55f76eab3eed573269524/langchain_text_splitters-1.1.1.tar.gz", hash = "sha256:34861abe7c07d9e49d4dc852d0129e26b32738b60a74486853ec9b6d6a8e01d2", size = 279352 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/66/d9e0c3b83b0ad75ee746c51ba347cacecb8d656b96e1d513f3e334d1ccab/langchain_text_splitters-1.1.1-py3-none-any.whl", hash = "sha256:5ed0d7bf314ba925041e7d7d17cd8b10f688300d5415fb26c29442f061e329dc", size = 35734, upload-time = "2026-02-18T23:02:41.913Z" }, + { url = "https://files.pythonhosted.org/packages/84/66/d9e0c3b83b0ad75ee746c51ba347cacecb8d656b96e1d513f3e334d1ccab/langchain_text_splitters-1.1.1-py3-none-any.whl", hash = "sha256:5ed0d7bf314ba925041e7d7d17cd8b10f688300d5415fb26c29442f061e329dc", size = 35734 }, ] [[package]] @@ -3609,9 +3601,9 @@ dependencies = [ { name = "langchain-core" }, { name = "unstructured-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/83/7d0821c03868d69c52a385891772a9c6f931a8a6cd7c16a2e397c01a5ee2/langchain_unstructured-1.0.1.tar.gz", hash = "sha256:9b4b832b8fcdef8598ff634ec6fc0e344e6d8fdae854c5727f717d605d28e406", size = 6418, upload-time = "2025-12-27T23:23:03.848Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/83/7d0821c03868d69c52a385891772a9c6f931a8a6cd7c16a2e397c01a5ee2/langchain_unstructured-1.0.1.tar.gz", hash = "sha256:9b4b832b8fcdef8598ff634ec6fc0e344e6d8fdae854c5727f717d605d28e406", size = 6418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/17/803613614fa4cec18d7dc9953a6029aa038ae4e693ef1b8375d08c30718c/langchain_unstructured-1.0.1-py3-none-any.whl", hash = "sha256:1cdb00b3bccc05daa6f03bc3991b0a76270fd2fc095b358c23bd08c5f0e05f50", size = 6942, upload-time = "2025-12-27T23:23:02.801Z" }, + { url = "https://files.pythonhosted.org/packages/cc/17/803613614fa4cec18d7dc9953a6029aa038ae4e693ef1b8375d08c30718c/langchain_unstructured-1.0.1-py3-none-any.whl", hash = "sha256:1cdb00b3bccc05daa6f03bc3991b0a76270fd2fc095b358c23bd08c5f0e05f50", size = 6942 }, ] [[package]] @@ -3621,7 +3613,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474, upload-time = "2021-05-07T07:54:13.562Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474 } [[package]] name = "langgraph" @@ -3635,9 +3627,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/b2/e7db624e8b0ee063ecfbf7acc09467c0836a05914a78e819dfb3744a0fac/langgraph-1.1.3.tar.gz", hash = "sha256:ee496c297a9c93b38d8560be15cbb918110f49077d83abd14976cb13ac3b3370", size = 545120, upload-time = "2026-03-18T23:42:58.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/b2/e7db624e8b0ee063ecfbf7acc09467c0836a05914a78e819dfb3744a0fac/langgraph-1.1.3.tar.gz", hash = "sha256:ee496c297a9c93b38d8560be15cbb918110f49077d83abd14976cb13ac3b3370", size = 545120 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/f7/221cc479e95e03e260496616e5ce6fb50c1ea01472e3a5bc481a9b8a2f83/langgraph-1.1.3-py3-none-any.whl", hash = "sha256:57cd6964ebab41cbd211f222293a2352404e55f8b2312cecde05e8753739b546", size = 168149, upload-time = "2026-03-18T23:42:56.967Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f7/221cc479e95e03e260496616e5ce6fb50c1ea01472e3a5bc481a9b8a2f83/langgraph-1.1.3-py3-none-any.whl", hash = "sha256:57cd6964ebab41cbd211f222293a2352404e55f8b2312cecde05e8753739b546", size = 168149 }, ] [[package]] @@ -3648,9 +3640,9 @@ dependencies = [ { name = "langchain-core" }, { name = "ormsgpack" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453 }, ] [[package]] @@ -3663,9 +3655,9 @@ dependencies = [ { name = "psycopg" }, { name = "psycopg-pool" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7a/8f439966643d32111248a225e6cb33a182d07c90de780c4dbfc1e0377832/langgraph_checkpoint_postgres-3.0.5.tar.gz", hash = "sha256:a8fd7278a63f4f849b5cbc7884a15ca8f41e7d5f7467d0a66b31e8c24492f7eb", size = 127856, upload-time = "2026-03-18T21:25:29.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/7a/8f439966643d32111248a225e6cb33a182d07c90de780c4dbfc1e0377832/langgraph_checkpoint_postgres-3.0.5.tar.gz", hash = "sha256:a8fd7278a63f4f849b5cbc7884a15ca8f41e7d5f7467d0a66b31e8c24492f7eb", size = 127856 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/87/b0f98b33a67204bca9d5619bcd9574222f6b025cf3c125eedcec9a50ecbc/langgraph_checkpoint_postgres-3.0.5-py3-none-any.whl", hash = "sha256:86d7040a88fd70087eaafb72251d796696a0a2d856168f5c11ef620771411552", size = 42907, upload-time = "2026-03-18T21:25:28.75Z" }, + { url = "https://files.pythonhosted.org/packages/e8/87/b0f98b33a67204bca9d5619bcd9574222f6b025cf3c125eedcec9a50ecbc/langgraph_checkpoint_postgres-3.0.5-py3-none-any.whl", hash = "sha256:86d7040a88fd70087eaafb72251d796696a0a2d856168f5c11ef620771411552", size = 42907 }, ] [[package]] @@ -3676,9 +3668,9 @@ dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648 }, ] [[package]] @@ -3689,9 +3681,9 @@ dependencies = [ { name = "httpx" }, { name = "orjson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/012f0e0f5c9fd26f92bdc9d244756ad673c428230156ef668e6ec7c18cee/langgraph_sdk-0.3.12.tar.gz", hash = "sha256:c9c9ec22b3c0fcd352e2b8f32a815164f69446b8648ca22606329f4ff4c59a71", size = 194932, upload-time = "2026-03-18T22:15:54.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/012f0e0f5c9fd26f92bdc9d244756ad673c428230156ef668e6ec7c18cee/langgraph_sdk-0.3.12.tar.gz", hash = "sha256:c9c9ec22b3c0fcd352e2b8f32a815164f69446b8648ca22606329f4ff4c59a71", size = 194932 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/4d/4f796e86b03878ab20d9b30aaed1ad459eda71a5c5b67f7cfe712f3548f2/langgraph_sdk-0.3.12-py3-none-any.whl", hash = "sha256:44323804965d6ec2a07127b3cf08a0428ea6deaeb172c2d478d5cd25540e3327", size = 95834, upload-time = "2026-03-18T22:15:53.545Z" }, + { url = "https://files.pythonhosted.org/packages/17/4d/4f796e86b03878ab20d9b30aaed1ad459eda71a5c5b67f7cfe712f3548f2/langgraph_sdk-0.3.12-py3-none-any.whl", hash = "sha256:44323804965d6ec2a07127b3cf08a0428ea6deaeb172c2d478d5cd25540e3327", size = 95834 }, ] [[package]] @@ -3709,18 +3701,18 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923, upload-time = "2026-03-19T22:45:23.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927, upload-time = "2026-03-19T22:45:21.603Z" }, + { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927 }, ] [[package]] name = "latex2mathml" version = "3.79.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dd/8d/2161f46485d9c36c0fa0e1c997faf08bb7843027e59b549598e49f55f8bf/latex2mathml-3.79.0.tar.gz", hash = "sha256:11bde318c2d2d6fcdd105a07509d867cee2208f653278eb80243dec7ea77a0ce", size = 151103, upload-time = "2026-03-12T23:25:08.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/8d/2161f46485d9c36c0fa0e1c997faf08bb7843027e59b549598e49f55f8bf/latex2mathml-3.79.0.tar.gz", hash = "sha256:11bde318c2d2d6fcdd105a07509d867cee2208f653278eb80243dec7ea77a0ce", size = 151103 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/92/56a954dd59637dd2ee013581fa3beea0821f17f2c07f818fc51dcc11fd10/latex2mathml-3.79.0-py3-none-any.whl", hash = "sha256:9f10720d4fcf6b22d1b81f6628237832419a7a29783c13aa92fa8d680165e63d", size = 73945, upload-time = "2026-03-12T23:25:09.466Z" }, + { url = "https://files.pythonhosted.org/packages/fd/92/56a954dd59637dd2ee013581fa3beea0821f17f2c07f818fc51dcc11fd10/latex2mathml-3.79.0-py3-none-any.whl", hash = "sha256:9f10720d4fcf6b22d1b81f6628237832419a7a29783c13aa92fa8d680165e63d", size = 73945 }, ] [[package]] @@ -3732,9 +3724,9 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/69/826a5d1f45426c68d8f6539f8d275c0e4fcaa57f0c017ec3100986558a41/limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da", size = 226104, upload-time = "2026-02-05T07:17:35.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/69/826a5d1f45426c68d8f6539f8d275c0e4fcaa57f0c017ec3100986558a41/limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da", size = 226104 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/98/cb5ca20618d205a09d5bec7591fbc4130369c7e6308d9a676a28ff3ab22c/limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8", size = 60954, upload-time = "2026-02-05T07:17:34.425Z" }, + { url = "https://files.pythonhosted.org/packages/b9/98/cb5ca20618d205a09d5bec7591fbc4130369c7e6308d9a676a28ff3ab22c/limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8", size = 60954 }, ] [[package]] @@ -3745,9 +3737,9 @@ dependencies = [ { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/fa/d54d7086ceb8e8aa3777ae82f9876ceb7d15a6f583bbebf2fc2bea4cf69c/linkup_sdk-0.13.0.tar.gz", hash = "sha256:dab3f516bb955bdb9dd5815445bfdc7a2c9803dc57c3b4be4038d9e40f3d096a", size = 76440, upload-time = "2026-03-02T13:09:25.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/fa/d54d7086ceb8e8aa3777ae82f9876ceb7d15a6f583bbebf2fc2bea4cf69c/linkup_sdk-0.13.0.tar.gz", hash = "sha256:dab3f516bb955bdb9dd5815445bfdc7a2c9803dc57c3b4be4038d9e40f3d096a", size = 76440 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/b8/9a8be932db54dc673c0a2f033831e9963cb4395352ce5a54a423e2fb58de/linkup_sdk-0.13.0-py3-none-any.whl", hash = "sha256:d4f5f4698cbaf4711a3296473f6030613c9c8ac829c83172a51c34c6e653808a", size = 11750, upload-time = "2026-03-02T13:09:24.553Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b8/9a8be932db54dc673c0a2f033831e9963cb4395352ce5a54a423e2fb58de/linkup_sdk-0.13.0-py3-none-any.whl", hash = "sha256:d4f5f4698cbaf4711a3296473f6030613c9c8ac829c83172a51c34c6e653808a", size = 11750 }, ] [[package]] @@ -3768,9 +3760,9 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/c095649380adc96c8630273c1768c2ad1e74aa2ee1dd8dd05d218a60569f/litellm-1.83.14.tar.gz", hash = "sha256:24aef9b47cdc424c833e32f3727f411741c690832cd1fe4405e0077144fe09c9", size = 14836599, upload-time = "2026-04-26T03:16:10.176Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/c095649380adc96c8630273c1768c2ad1e74aa2ee1dd8dd05d218a60569f/litellm-1.83.14.tar.gz", hash = "sha256:24aef9b47cdc424c833e32f3727f411741c690832cd1fe4405e0077144fe09c9", size = 14836599 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/5c/1b5691575420135e90578543b2bf219497caa33cfd0af64cb38f30288450/litellm-1.83.14-py3-none-any.whl", hash = "sha256:92b11ba2a32cf80707ddf388d18526696c7999a21b418c5e3b6eda1243d2cfdb", size = 16457054, upload-time = "2026-04-26T03:16:05.72Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5c/1b5691575420135e90578543b2bf219497caa33cfd0af64cb38f30288450/litellm-1.83.14-py3-none-any.whl", hash = "sha256:92b11ba2a32cf80707ddf388d18526696c7999a21b418c5e3b6eda1243d2cfdb", size = 16457054 }, ] [[package]] @@ -3782,9 +3774,9 @@ dependencies = [ { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f3/f4d6520f8d546e6c5a02f6ebeed5c09774a074b8d2c24ad559ace97a56a6/llama_cloud-0.1.46.tar.gz", hash = "sha256:e86f8791c053590d70cc59e0fc13ce72f9b681a8e658bc61df86d0285288d8ee", size = 127752, upload-time = "2026-01-21T18:40:57.103Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f3/f4d6520f8d546e6c5a02f6ebeed5c09774a074b8d2c24ad559ace97a56a6/llama_cloud-0.1.46.tar.gz", hash = "sha256:e86f8791c053590d70cc59e0fc13ce72f9b681a8e658bc61df86d0285288d8ee", size = 127752 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/3a/6caaea28c8c804add33c91d356ed7d5a5412d6c9598e1450af95a15e0bcd/llama_cloud-0.1.46-py3-none-any.whl", hash = "sha256:6c6546c09c04a038c86d84d42f00eae8fd3bff49991ad3aab844bd866ecdf352", size = 361989, upload-time = "2026-01-21T18:40:54.863Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/6caaea28c8c804add33c91d356ed7d5a5412d6c9598e1450af95a15e0bcd/llama_cloud-0.1.46-py3-none-any.whl", hash = "sha256:6c6546c09c04a038c86d84d42f00eae8fd3bff49991ad3aab844bd866ecdf352", size = 361989 }, ] [[package]] @@ -3801,9 +3793,9 @@ dependencies = [ { name = "python-dotenv" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/91/c3c94a58c44d0a12e0df2d5038b188fc283877f56cf2f6c41c60f43258e6/llama_cloud_services-0.6.94.tar.gz", hash = "sha256:127b8440d3d3a964d0c4b3f5fe7fcac3ead482f7645971cc8ae30768dcf63306", size = 64114, upload-time = "2026-02-13T23:29:40.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/91/c3c94a58c44d0a12e0df2d5038b188fc283877f56cf2f6c41c60f43258e6/llama_cloud_services-0.6.94.tar.gz", hash = "sha256:127b8440d3d3a964d0c4b3f5fe7fcac3ead482f7645971cc8ae30768dcf63306", size = 64114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/ab/876486e4f1c137cfeca8f876abd18eeec35a66a0fd8adb15afba7b28aa8c/llama_cloud_services-0.6.94-py3-none-any.whl", hash = "sha256:ac89785f3689d71298511f751bcf4ca16952a616bd75ff06e0ff164f04b0775b", size = 77098, upload-time = "2026-02-13T23:29:38.958Z" }, + { url = "https://files.pythonhosted.org/packages/14/ab/876486e4f1c137cfeca8f876abd18eeec35a66a0fd8adb15afba7b28aa8c/llama_cloud_services-0.6.94-py3-none-any.whl", hash = "sha256:ac89785f3689d71298511f751bcf4ca16952a616bd75ff06e0ff164f04b0775b", size = 77098 }, ] [[package]] @@ -3840,9 +3832,9 @@ dependencies = [ { name = "typing-inspect" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/eb/a661cc2f70177f59cfe7bfcdb7a4e9352fb073ab46927068151bf2905fbb/llama_index_core-0.14.19.tar.gz", hash = "sha256:7b17f321f0d965495402890991b2bfde49d4197bc46ca5970300cc7b9c2df6a2", size = 11599592, upload-time = "2026-03-25T20:58:25.751Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/eb/a661cc2f70177f59cfe7bfcdb7a4e9352fb073ab46927068151bf2905fbb/llama_index_core-0.14.19.tar.gz", hash = "sha256:7b17f321f0d965495402890991b2bfde49d4197bc46ca5970300cc7b9c2df6a2", size = 11599592 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b6/6c2678b8597903503b804fe831a203d299bcbcc07bdf35789a484e67f7c0/llama_index_core-0.14.19-py3-none-any.whl", hash = "sha256:807352f16a300f9980d0110cfdaa81d07e201384965e9f7d940c8ead80d463ed", size = 11945679, upload-time = "2026-03-25T20:58:28.265Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/6c2678b8597903503b804fe831a203d299bcbcc07bdf35789a484e67f7c0/llama_index_core-0.14.19-py3-none-any.whl", hash = "sha256:807352f16a300f9980d0110cfdaa81d07e201384965e9f7d940c8ead80d463ed", size = 11945679 }, ] [[package]] @@ -3853,9 +3845,9 @@ dependencies = [ { name = "deprecated" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d0/671b23ccff255c9bce132a84ffd5a6f4541ceefdeab9c1786b08c9722f2e/llama_index_instrumentation-0.5.0.tar.gz", hash = "sha256:eeb724648b25d149de882a5ac9e21c5acb1ce780da214bda2b075341af29ad8e", size = 43831, upload-time = "2026-03-12T20:17:06.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/d0/671b23ccff255c9bce132a84ffd5a6f4541ceefdeab9c1786b08c9722f2e/llama_index_instrumentation-0.5.0.tar.gz", hash = "sha256:eeb724648b25d149de882a5ac9e21c5acb1ce780da214bda2b075341af29ad8e", size = 43831 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/45/6dcaccef44e541ffa138e4b45e33e0d40ab2a7d845338483954fcf77bc75/llama_index_instrumentation-0.5.0-py3-none-any.whl", hash = "sha256:aaab83cddd9dd434278891012d8995f47a3bc7ed1736a371db90965348c56a21", size = 16444, upload-time = "2026-03-12T20:17:05.957Z" }, + { url = "https://files.pythonhosted.org/packages/c3/45/6dcaccef44e541ffa138e4b45e33e0d40ab2a7d845338483954fcf77bc75/llama_index_instrumentation-0.5.0-py3-none-any.whl", hash = "sha256:aaab83cddd9dd434278891012d8995f47a3bc7ed1736a371db90965348c56a21", size = 16444 }, ] [[package]] @@ -3867,29 +3859,29 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/a8/2198a81d96394686f598857d12164256ce11699b99a76fdbaf38b8bc1a2c/llama_index_workflows-2.17.1.tar.gz", hash = "sha256:c62fabe509cf0003ddfe5b2b27f48b3443c7c9a84e9cdc904c6f9ed5f8cbe25d", size = 86723, upload-time = "2026-03-20T15:45:14.216Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a8/2198a81d96394686f598857d12164256ce11699b99a76fdbaf38b8bc1a2c/llama_index_workflows-2.17.1.tar.gz", hash = "sha256:c62fabe509cf0003ddfe5b2b27f48b3443c7c9a84e9cdc904c6f9ed5f8cbe25d", size = 86723 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/28/20dc2db83adc2d9a11e042eac568f52788eb850e9381ffb1087d51f46672/llama_index_workflows-2.17.1-py3-none-any.whl", hash = "sha256:0d78fc36c2ab5430887c9f34367d59d4c22cf1e6c40ecdc3596214234c2b5010", size = 110539, upload-time = "2026-03-20T15:45:15.341Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/20dc2db83adc2d9a11e042eac568f52788eb850e9381ffb1087d51f46672/llama_index_workflows-2.17.1-py3-none-any.whl", hash = "sha256:0d78fc36c2ab5430887c9f34367d59d4c22cf1e6c40ecdc3596214234c2b5010", size = 110539 }, ] [[package]] name = "llvmlite" version = "0.46.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, - { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, - { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, - { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, - { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, - { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" }, - { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" }, - { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767 }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176 }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630 }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940 }, + { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767 }, + { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176 }, + { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629 }, + { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941 }, + { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768 }, + { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173 }, + { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628 }, + { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056 }, ] [[package]] @@ -3900,89 +3892,89 @@ dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "win32-setctime", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, ] [[package]] name = "lxml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, - { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, - { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, - { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, - { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, - { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, - { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, - { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, - { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, - { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, - { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, - { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, - { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, - { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, - { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, - { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, - { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, - { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, - { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, - { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, - { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, - { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, - { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, - { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887 }, + { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818 }, + { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807 }, + { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179 }, + { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044 }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685 }, + { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127 }, + { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958 }, + { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541 }, + { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426 }, + { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917 }, + { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795 }, + { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759 }, + { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666 }, + { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989 }, + { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456 }, + { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793 }, + { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836 }, + { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494 }, + { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146 }, + { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932 }, + { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060 }, + { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000 }, + { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496 }, + { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779 }, + { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072 }, + { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171 }, + { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175 }, + { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688 }, + { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655 }, + { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695 }, + { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841 }, + { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700 }, + { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347 }, + { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248 }, + { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801 }, + { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403 }, + { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974 }, + { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953 }, + { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054 }, + { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421 }, + { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684 }, + { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463 }, + { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437 }, + { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890 }, + { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185 }, + { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895 }, + { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246 }, + { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797 }, + { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404 }, + { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072 }, + { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617 }, + { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930 }, + { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380 }, + { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632 }, + { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171 }, + { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109 }, + { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061 }, + { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233 }, + { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739 }, + { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119 }, + { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665 }, + { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997 }, + { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957 }, + { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372 }, + { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653 }, + { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795 }, + { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023 }, + { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420 }, + { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837 }, + { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205 }, ] [package.optional-dependencies] @@ -3997,9 +3989,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/a4/5c62acfacd69ff4f5db395100f5cfb9b54e7ac8c69a235e4e939fd13f021/lxml_html_clean-0.4.4.tar.gz", hash = "sha256:58f39a9d632711202ed1d6d0b9b47a904e306c85de5761543b90e3e3f736acfb", size = 23899, upload-time = "2026-02-27T09:35:52.911Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/a4/5c62acfacd69ff4f5db395100f5cfb9b54e7ac8c69a235e4e939fd13f021/lxml_html_clean-0.4.4.tar.gz", hash = "sha256:58f39a9d632711202ed1d6d0b9b47a904e306c85de5761543b90e3e3f736acfb", size = 23899 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/76/7ffc1d3005cf7749123bc47cb3ea343cd97b0ac2211bab40f57283577d0e/lxml_html_clean-0.4.4-py3-none-any.whl", hash = "sha256:ce2ef506614ecb85ee1c5fe0a2aa45b06a19514ec7949e9c8f34f06925cfabcb", size = 14565, upload-time = "2026-02-27T09:35:51.86Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/7ffc1d3005cf7749123bc47cb3ea343cd97b0ac2211bab40f57283577d0e/lxml_html_clean-0.4.4-py3-none-any.whl", hash = "sha256:ce2ef506614ecb85ee1c5fe0a2aa45b06a19514ec7949e9c8f34f06925cfabcb", size = 14565 }, ] [[package]] @@ -4010,21 +4002,21 @@ dependencies = [ { name = "click" }, { name = "onnxruntime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/ca/dfb30534be5ad84363e0e8ce08bc6e990ce0430aec1eaafb0633b4bb3f7f/magika-1.0.2.tar.gz", hash = "sha256:8ed912d8f14d044f43fdbd17d6bd2cbdd6e8b8246e89be49f6cd547053636677", size = 3041955, upload-time = "2026-02-25T16:07:03.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/ca/dfb30534be5ad84363e0e8ce08bc6e990ce0430aec1eaafb0633b4bb3f7f/magika-1.0.2.tar.gz", hash = "sha256:8ed912d8f14d044f43fdbd17d6bd2cbdd6e8b8246e89be49f6cd547053636677", size = 3041955 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/46/b8180a34c64470e2f40a3676ef3284a32efd2b3598aa99946ee319eb66e8/magika-1.0.2-py3-none-any.whl", hash = "sha256:c50be7a6a7132ef1a92956694401aaf911bda8fc5e2a591092e0dac5b5865a8a", size = 2969547, upload-time = "2026-02-25T16:06:55.987Z" }, - { url = "https://files.pythonhosted.org/packages/38/f3/a65650c36a472fed1ca1c4868e567cf015c14c73a6bb5fa4a808932e0944/magika-1.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1db8e2d57556e7244f5fce9cfd023aa0da05d204ea7313f3c75b32feab2bcd6d", size = 13811935, upload-time = "2026-02-25T16:06:57.589Z" }, - { url = "https://files.pythonhosted.org/packages/ba/9e/429608833917b7d4c4f7071a270bbca96821fb592e275d85bc9eae5a94c8/magika-1.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:df4706c18153431548b1d36c8ca11c8a8a415197dcc741281846c61ebfc94a5b", size = 15924817, upload-time = "2026-02-25T16:06:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/1a/12/185a8822994a2f7b5e7d88d19a88d80637917bbb0a6f3f59a2564aabc125/magika-1.0.2-py3-none-win_amd64.whl", hash = "sha256:4937e876d55642423d6416e5db4e5ca7523ab7f855cbc5389efdeac1d149df04", size = 13099543, upload-time = "2026-02-25T16:07:01.942Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/b8180a34c64470e2f40a3676ef3284a32efd2b3598aa99946ee319eb66e8/magika-1.0.2-py3-none-any.whl", hash = "sha256:c50be7a6a7132ef1a92956694401aaf911bda8fc5e2a591092e0dac5b5865a8a", size = 2969547 }, + { url = "https://files.pythonhosted.org/packages/38/f3/a65650c36a472fed1ca1c4868e567cf015c14c73a6bb5fa4a808932e0944/magika-1.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1db8e2d57556e7244f5fce9cfd023aa0da05d204ea7313f3c75b32feab2bcd6d", size = 13811935 }, + { url = "https://files.pythonhosted.org/packages/ba/9e/429608833917b7d4c4f7071a270bbca96821fb592e275d85bc9eae5a94c8/magika-1.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:df4706c18153431548b1d36c8ca11c8a8a415197dcc741281846c61ebfc94a5b", size = 15924817 }, + { url = "https://files.pythonhosted.org/packages/1a/12/185a8822994a2f7b5e7d88d19a88d80637917bbb0a6f3f59a2564aabc125/magika-1.0.2-py3-none-win_amd64.whl", hash = "sha256:4937e876d55642423d6416e5db4e5ca7523ab7f855cbc5389efdeac1d149df04", size = 13099543 }, ] [[package]] name = "makefun" version = "1.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/cf/6780ab8bc3b84a1cce3e4400aed3d64b6db7d5e227a2f75b6ded5674701a/makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947", size = 73565, upload-time = "2025-05-09T15:00:42.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/cf/6780ab8bc3b84a1cce3e4400aed3d64b6db7d5e227a2f75b6ded5674701a/makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947", size = 73565 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/c0/4bc973defd1270b89ccaae04cef0d5fa3ea85b59b108ad2c08aeea9afb76/makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4", size = 22923, upload-time = "2025-05-09T15:00:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/4bc973defd1270b89ccaae04cef0d5fa3ea85b59b108ad2c08aeea9afb76/makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4", size = 22923 }, ] [[package]] @@ -4034,18 +4026,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474 } 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" }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509 }, ] [[package]] name = "markdown" version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180 }, ] [[package]] @@ -4055,9 +4047,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } 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" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, ] [[package]] @@ -4068,81 +4060,81 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/bc/c8c8eea5335341306b0fa7e1cb33c5e1c8d24ef70ddd684da65f41c49c92/markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09", size = 18816, upload-time = "2025-11-16T19:21:18.565Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/bc/c8c8eea5335341306b0fa7e1cb33c5e1c8d24ef70ddd684da65f41c49c92/markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09", size = 18816 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ce/f1e3e9d959db134cedf06825fae8d5b294bd368aacdd0831a3975b7c4d55/markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a", size = 15724, upload-time = "2025-11-16T19:21:17.622Z" }, + { url = "https://files.pythonhosted.org/packages/43/ce/f1e3e9d959db134cedf06825fae8d5b294bd368aacdd0831a3975b7c4d55/markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a", size = 15724 }, ] [[package]] name = "marko" version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/2f/050b6d485f052ddf17d76a41f9334d6fb2a8a85df35347a12d97ed3bc5c1/marko-2.2.2.tar.gz", hash = "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", size = 143641, upload-time = "2026-01-05T11:04:41.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/2f/050b6d485f052ddf17d76a41f9334d6fb2a8a85df35347a12d97ed3bc5c1/marko-2.2.2.tar.gz", hash = "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", size = 143641 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/f8/36d79bac5701e6786f9880c61bbe57574760a13c1af84ab71e5ed21faecc/marko-2.2.2-py3-none-any.whl", hash = "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e", size = 42701, upload-time = "2026-01-05T11:04:40.75Z" }, + { url = "https://files.pythonhosted.org/packages/83/f8/36d79bac5701e6786f9880c61bbe57574760a13c1af84ab71e5ed21faecc/marko-2.2.2-py3-none-any.whl", hash = "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e", size = 42701 }, ] [[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" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } wheels = [ - { 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/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, ] [[package]] @@ -4152,9 +4144,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/79/de6c16cc902f4fc372236926b0ce2ab7845268dcc30fb2fbb7f71b418631/marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57", size = 222095, upload-time = "2025-12-22T06:53:53.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/79/de6c16cc902f4fc372236926b0ce2ab7845268dcc30fb2fbb7f71b418631/marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57", size = 222095 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964, upload-time = "2025-12-22T06:53:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964 }, ] [[package]] @@ -4172,43 +4164,43 @@ dependencies = [ { name = "pyparsing", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "python-dateutil", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453 }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321 }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944 }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099 }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040 }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717 }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751 }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076 }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794 }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474 }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637 }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678 }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686 }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917 }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679 }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336 }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653 }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356 }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000 }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043 }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075 }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481 }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473 }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896 }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444 }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719 }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205 }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785 }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361 }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357 }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610 }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011 }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801 }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560 }, ] [[package]] @@ -4231,18 +4223,18 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615 }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] @@ -4253,9 +4245,9 @@ dependencies = [ { name = "addict" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/c7/fb01370a76585b46595a01b52f18e65c8ba6d7a313a05e5d9fff0a8e1c69/misaki-0.9.4.tar.gz", hash = "sha256:3960fa3e6de179a90ee8e628446a4a4f6b8c730b6e3410999cf396189f4d9c40", size = 3756765, upload-time = "2025-04-05T21:57:14.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c7/fb01370a76585b46595a01b52f18e65c8ba6d7a313a05e5d9fff0a8e1c69/misaki-0.9.4.tar.gz", hash = "sha256:3960fa3e6de179a90ee8e628446a4a4f6b8c730b6e3410999cf396189f4d9c40", size = 3756765 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/ec/0ee4110ddb54278b8f21c40a140370ae8f687036c4edf578316602697c56/misaki-0.9.4-py3-none-any.whl", hash = "sha256:90e2eeb169786c014c429e5058d2ea6bcd02d651f2a24450ba6c9ffc0f8da15a", size = 3617774, upload-time = "2025-04-05T21:57:10.678Z" }, + { url = "https://files.pythonhosted.org/packages/82/ec/0ee4110ddb54278b8f21c40a140370ae8f687036c4edf578316602697c56/misaki-0.9.4-py3-none-any.whl", hash = "sha256:90e2eeb169786c014c429e5058d2ea6bcd02d651f2a24450ba6c9ffc0f8da15a", size = 3617774 }, ] [package.optional-dependencies] @@ -4271,9 +4263,9 @@ en = [ name = "mistune" version = "3.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598 }, ] [[package]] @@ -4283,115 +4275,115 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, - { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, - { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, - { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, - { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, - { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, - { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, - { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, - { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, - { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, - { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, - { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, - { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, - { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, - { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, - { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927 }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464 }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002 }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222 }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793 }, + { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888 }, + { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993 }, + { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956 }, + { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224 }, + { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798 }, + { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083 }, + { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111 }, + { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453 }, + { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612 }, + { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145 }, + { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781 }, + { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145 }, + { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230 }, + { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032 }, + { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353 }, + { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085 }, + { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358 }, + { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332 }, + { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612 }, + { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825 }, ] [[package]] name = "mmh3" version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, - { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, - { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, - { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, - { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, - { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, - { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, - { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, - { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, - { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819, upload-time = "2026-03-05T15:54:46.509Z" }, - { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033, upload-time = "2026-03-05T15:54:50.994Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299, upload-time = "2026-03-05T15:54:53.569Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222, upload-time = "2026-03-05T15:54:55.092Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048, upload-time = "2026-03-05T15:54:56.305Z" }, - { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742, upload-time = "2026-03-05T15:54:57.453Z" }, - { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132, upload-time = "2026-03-05T15:54:58.583Z" }, - { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479, upload-time = "2026-03-05T15:55:01.532Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030, upload-time = "2026-03-05T15:55:02.934Z" }, - { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536, upload-time = "2026-03-05T15:55:04.135Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, - { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, - { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, - { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, - { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, - { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, - { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, - { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, - { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, - { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, - { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, - { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, - { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, - { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, - { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, - { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, - { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, - { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, - { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, + { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087 }, + { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500 }, + { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034 }, + { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292 }, + { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274 }, + { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158 }, + { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005 }, + { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744 }, + { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111 }, + { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623 }, + { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437 }, + { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002 }, + { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507 }, + { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773 }, + { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560 }, + { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313 }, + { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832 }, + { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964 }, + { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114 }, + { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819 }, + { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330 }, + { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078 }, + { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498 }, + { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033 }, + { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320 }, + { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299 }, + { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222 }, + { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048 }, + { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742 }, + { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132 }, + { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686 }, + { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479 }, + { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030 }, + { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536 }, + { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769 }, + { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563 }, + { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310 }, + { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794 }, + { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825 }, + { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344 }, + { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291 }, + { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052 }, + { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311 }, + { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279 }, + { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290 }, + { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116 }, + { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740 }, + { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143 }, + { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703 }, + { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484 }, + { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012 }, + { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508 }, + { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387 }, + { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101 }, + { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836 }, + { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727 }, + { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308 }, + { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758 }, + { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670 }, + { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399 }, + { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386 }, + { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924 }, + { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280 }, + { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050 }, + { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158 }, + { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890 }, + { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121 }, + { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187 }, + { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036 }, + { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391 }, ] [[package]] @@ -4408,18 +4400,18 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/d2/0da831d4f7930839340ec9cab3d9c82205cf65b96bdc29cab6796b795b68/model2vec-0.8.1.tar.gz", hash = "sha256:9a35d35f6a444e4cec19f2027ee106c54965cd26b7fd4a4f002b5f3e2b6777f4", size = 4535650, upload-time = "2026-03-27T13:38:41.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/d2/0da831d4f7930839340ec9cab3d9c82205cf65b96bdc29cab6796b795b68/model2vec-0.8.1.tar.gz", hash = "sha256:9a35d35f6a444e4cec19f2027ee106c54965cd26b7fd4a4f002b5f3e2b6777f4", size = 4535650 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/5d/fa38e1f68d123726639377f0ba494b366905a2fa451d2c0f365cf0cc6ecb/model2vec-0.8.1-py3-none-any.whl", hash = "sha256:24eee3a5b060c705cceddc9091e0d23fa908fb967e48c0e68b7990211b10927d", size = 49938, upload-time = "2026-03-27T13:38:39.81Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5d/fa38e1f68d123726639377f0ba494b366905a2fa451d2c0f365cf0cc6ecb/model2vec-0.8.1-py3-none-any.whl", hash = "sha256:24eee3a5b060c705cceddc9091e0d23fa908fb967e48c0e68b7990211b10927d", size = 49938 }, ] [[package]] name = "more-itertools" version = "10.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667 }, ] [[package]] @@ -4431,9 +4423,9 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270, upload-time = "2024-05-07T14:00:31.815Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756, upload-time = "2024-05-07T14:00:29.633Z" }, + { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756 }, ] [package.optional-dependencies] @@ -4445,9 +4437,9 @@ dill = [ name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, ] [[package]] @@ -4459,9 +4451,9 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/aa/5a646093ac218e4a329391d5a31e5092a89db7d2ef1637a90b82cd0b6f94/msal-1.35.1.tar.gz", hash = "sha256:70cac18ab80a053bff86219ba64cfe3da1f307c74b009e2da57ef040eb1b5656", size = 165658, upload-time = "2026-03-04T23:38:51.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/aa/5a646093ac218e4a329391d5a31e5092a89db7d2ef1637a90b82cd0b6f94/msal-1.35.1.tar.gz", hash = "sha256:70cac18ab80a053bff86219ba64cfe3da1f307c74b009e2da57ef040eb1b5656", size = 165658 } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/86/16815fddf056ca998853c6dc525397edf0b43559bb4073a80d2bc7fe8009/msal-1.35.1-py3-none-any.whl", hash = "sha256:8f4e82f34b10c19e326ec69f44dc6b30171f2f7098f3720ea8a9f0c11832caa3", size = 119909, upload-time = "2026-03-04T23:38:50.452Z" }, + { url = "https://files.pythonhosted.org/packages/96/86/16815fddf056ca998853c6dc525397edf0b43559bb4073a80d2bc7fe8009/msal-1.35.1-py3-none-any.whl", hash = "sha256:8f4e82f34b10c19e326ec69f44dc6b30171f2f7098f3720ea8a9f0c11832caa3", size = 119909 }, ] [[package]] @@ -4471,9 +4463,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "msal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 }, ] [[package]] @@ -4484,108 +4476,108 @@ dependencies = [ { name = "cryptography" }, { name = "olefile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/34/6250bdddaeaae24098e45449ea362fb3555a65fba30cad0ad5630ea48d1a/msoffcrypto_tool-6.0.0.tar.gz", hash = "sha256:9a5ebc4c0096b42e5d7ebc2350afdc92dc511061e935ca188468094fdd032bbe", size = 40593, upload-time = "2026-01-12T08:59:56.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/34/6250bdddaeaae24098e45449ea362fb3555a65fba30cad0ad5630ea48d1a/msoffcrypto_tool-6.0.0.tar.gz", hash = "sha256:9a5ebc4c0096b42e5d7ebc2350afdc92dc511061e935ca188468094fdd032bbe", size = 40593 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/85/9e359fa9279e1d6861faaf9b6f037a3226374deb20a054c3937be6992013/msoffcrypto_tool-6.0.0-py3-none-any.whl", hash = "sha256:46c394ed5d9641e802fc79bf3fb0666a53748b23fa8c4aa634ae9d30d46fe397", size = 48791, upload-time = "2026-01-12T08:59:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/3c/85/9e359fa9279e1d6861faaf9b6f037a3226374deb20a054c3937be6992013/msoffcrypto_tool-6.0.0-py3-none-any.whl", hash = "sha256:46c394ed5d9641e802fc79bf3fb0666a53748b23fa8c4aa634ae9d30d46fe397", size = 48791 }, ] [[package]] name = "multidict" version = "6.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, - { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, - { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, - { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, - { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, - { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, - { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, - { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, - { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, - { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, - { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, - { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, - { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, - { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, - { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, - { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, - { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, - { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, - { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, - { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, - { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, - { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, - { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, - { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, - { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, - { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, - { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, - { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, - { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, - { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, - { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, - { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, - { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, - { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893 }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456 }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872 }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018 }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883 }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413 }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404 }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456 }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322 }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955 }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254 }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059 }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588 }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642 }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377 }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887 }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053 }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307 }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174 }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116 }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524 }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368 }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952 }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317 }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132 }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140 }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277 }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291 }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156 }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742 }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221 }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664 }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490 }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695 }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884 }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122 }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175 }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460 }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930 }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582 }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031 }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596 }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492 }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899 }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970 }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060 }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888 }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554 }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341 }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391 }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422 }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770 }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109 }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573 }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190 }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486 }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219 }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132 }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420 }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510 }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786 }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483 }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403 }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315 }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528 }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784 }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980 }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602 }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930 }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074 }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471 }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401 }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143 }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507 }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358 }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884 }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878 }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542 }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403 }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889 }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982 }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415 }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337 }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788 }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842 }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237 }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008 }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542 }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719 }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319 }, ] [[package]] @@ -4595,122 +4587,122 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603, upload-time = "2024-01-28T18:52:34.85Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824, upload-time = "2024-01-28T18:52:26.062Z" }, - { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519, upload-time = "2024-01-28T18:52:28.115Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741, upload-time = "2024-01-28T18:52:29.395Z" }, - { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628, upload-time = "2024-01-28T18:52:30.853Z" }, - { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351, upload-time = "2024-01-28T18:52:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, + { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 }, + { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 }, + { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628 }, + { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 }, ] [[package]] name = "murmurhash" version = "1.0.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291, upload-time = "2025-11-14T09:51:15.272Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884, upload-time = "2025-11-14T09:50:13.133Z" }, - { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855, upload-time = "2025-11-14T09:50:14.859Z" }, - { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088, upload-time = "2025-11-14T09:50:15.958Z" }, - { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978, upload-time = "2025-11-14T09:50:17.572Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956, upload-time = "2025-11-14T09:50:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184, upload-time = "2025-11-14T09:50:19.941Z" }, - { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647, upload-time = "2025-11-14T09:50:21.049Z" }, - { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338, upload-time = "2025-11-14T09:50:22.359Z" }, - { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861, upload-time = "2025-11-14T09:50:23.804Z" }, - { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840, upload-time = "2025-11-14T09:50:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080, upload-time = "2025-11-14T09:50:26.566Z" }, - { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648, upload-time = "2025-11-14T09:50:27.788Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502, upload-time = "2025-11-14T09:50:29.339Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736, upload-time = "2025-11-14T09:50:30.545Z" }, - { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682, upload-time = "2025-11-14T09:50:31.624Z" }, - { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370, upload-time = "2025-11-14T09:50:33.264Z" }, - { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955, upload-time = "2025-11-14T09:50:34.488Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108, upload-time = "2025-11-14T09:50:35.53Z" }, - { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054, upload-time = "2025-11-14T09:50:36.591Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153, upload-time = "2025-11-14T09:50:37.856Z" }, - { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345, upload-time = "2025-11-14T09:50:39.346Z" }, - { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990, upload-time = "2025-11-14T09:50:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812, upload-time = "2025-11-14T09:50:41.971Z" }, - { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398, upload-time = "2025-11-14T09:50:43.023Z" }, - { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029, upload-time = "2025-11-14T09:50:44.124Z" }, - { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912, upload-time = "2025-11-14T09:50:45.266Z" }, - { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847, upload-time = "2025-11-14T09:50:46.819Z" }, - { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267, upload-time = "2025-11-14T09:50:48.298Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894, upload-time = "2025-11-14T09:50:49.485Z" }, - { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054, upload-time = "2025-11-14T09:50:50.747Z" }, - { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579, upload-time = "2025-11-14T09:50:52.278Z" }, - { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341, upload-time = "2025-11-14T09:50:53.295Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146, upload-time = "2025-11-14T09:50:54.705Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141, upload-time = "2025-11-14T09:50:55.829Z" }, - { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898, upload-time = "2025-11-14T09:50:56.946Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040, upload-time = "2025-11-14T09:50:58.234Z" }, - { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239, upload-time = "2025-11-14T09:50:59.95Z" }, - { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811, upload-time = "2025-11-14T09:51:01.289Z" }, - { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817, upload-time = "2025-11-14T09:51:02.493Z" }, - { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219, upload-time = "2025-11-14T09:51:03.563Z" }, + { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884 }, + { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855 }, + { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088 }, + { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978 }, + { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956 }, + { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184 }, + { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647 }, + { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338 }, + { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861 }, + { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840 }, + { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080 }, + { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648 }, + { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736 }, + { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682 }, + { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370 }, + { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955 }, + { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108 }, + { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054 }, + { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153 }, + { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345 }, + { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812 }, + { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398 }, + { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029 }, + { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912 }, + { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847 }, + { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267 }, + { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894 }, + { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054 }, + { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579 }, + { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341 }, + { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146 }, + { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141 }, + { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898 }, + { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040 }, + { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239 }, + { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811 }, + { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817 }, + { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219 }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, ] [[package]] name = "nest-asyncio" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, ] [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, ] [[package]] name = "nh3" version = "0.3.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/86/f8d3a7c9bd1bbaa181f6312c757e0b74d25f71ecf84ea3c0dc5e0f01840d/nh3-0.3.4.tar.gz", hash = "sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade", size = 19520, upload-time = "2026-03-25T10:57:30.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/86/f8d3a7c9bd1bbaa181f6312c757e0b74d25f71ecf84ea3c0dc5e0f01840d/nh3-0.3.4.tar.gz", hash = "sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade", size = 19520 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/5e/c400663d14be2216bc084ed2befc871b7b12563f85d40904f2a4bf0dd2b7/nh3-0.3.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f", size = 1417991, upload-time = "2026-03-25T10:56:59.13Z" }, - { url = "https://files.pythonhosted.org/packages/36/f5/109526f5002ec41322ac8cafd50f0f154bae0c26b9607c0fcb708bdca8ec/nh3-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f", size = 790566, upload-time = "2026-03-25T10:57:00.445Z" }, - { url = "https://files.pythonhosted.org/packages/7b/66/38950f2b4b316ffd82ee51ed8f9143d1f56fdd620312cacc91613b77b3e7/nh3-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df", size = 837538, upload-time = "2026-03-25T10:57:01.848Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9f/9d6da970e9524fe360ea02a2082856390c2c8ba540409d1be6e5851887b3/nh3-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8", size = 1012154, upload-time = "2026-03-25T10:57:03.592Z" }, - { url = "https://files.pythonhosted.org/packages/54/92/7c85c33c241e9dd51dda115bd3f765e940446588cdaaca62ef8edffe675f/nh3-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af", size = 1092516, upload-time = "2026-03-25T10:57:04.726Z" }, - { url = "https://files.pythonhosted.org/packages/16/0f/597842bdb2890999a3faa2f3fcb02db8aa6ad09320d3d843ff6d0a1f737b/nh3-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5", size = 1053793, upload-time = "2026-03-25T10:57:06.171Z" }, - { url = "https://files.pythonhosted.org/packages/7d/32/669da65147bc10746d2e1d7a8a3dbfbffe0315f419e74b559e2ee3471a01/nh3-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89", size = 1035975, upload-time = "2026-03-25T10:57:07.292Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/9e97a8b3c5161c79b4bf21cc54e9334860a52cc54ede15bf2239ef494b73/nh3-0.3.4-cp314-cp314t-win32.whl", hash = "sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b", size = 600419, upload-time = "2026-03-25T10:57:08.342Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c7/6849d8d4295d3997d148eacb2d4b1c9faada4895ee3c1b1e12e72f4611e2/nh3-0.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1", size = 613342, upload-time = "2026-03-25T10:57:09.593Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0e/14a3f510f36c20b922c123a2730f071f938d006fb513aacfd46d6cbc03a7/nh3-0.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e", size = 607025, upload-time = "2026-03-25T10:57:10.959Z" }, - { url = "https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49", size = 1439519, upload-time = "2026-03-25T10:57:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0", size = 833798, upload-time = "2026-03-25T10:57:13.264Z" }, - { url = "https://files.pythonhosted.org/packages/6b/19/9487790780b8c94eacca37866c1270b747a4af8e244d43b3b550fddbbf62/nh3-0.3.4-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44", size = 820414, upload-time = "2026-03-25T10:57:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b4/c6a340dd321d20b1e4a663307032741da045685c87403926c43656f6f5ec/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21", size = 1061531, upload-time = "2026-03-25T10:57:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/c4/49/f6b4b474e0032e4bcbb7174b44e4cf6915670e09c62421deb06ccfcb88b8/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382", size = 1021889, upload-time = "2026-03-25T10:57:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/43/da/e52a6941746d1f974752af3fc8591f1dbcdcf7fd8c726c7d99f444ba820e/nh3-0.3.4-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4", size = 912965, upload-time = "2026-03-25T10:57:17.624Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304", size = 804975, upload-time = "2026-03-25T10:57:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/a9/56/b1275aa2c6510191eed76178da4626b0900402439cb9f27d6b9bf7c6d5e9/nh3-0.3.4-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0", size = 833400, upload-time = "2026-03-25T10:57:20.086Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a5/5d574ffa3c6e49a5364d1b25ebad165501c055340056671493beb467a15e/nh3-0.3.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5", size = 854277, upload-time = "2026-03-25T10:57:21.433Z" }, - { url = "https://files.pythonhosted.org/packages/79/36/8aeb2ab21517cefa212db109e41024e02650716cb42bf293d0a88437a92d/nh3-0.3.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003", size = 1022021, upload-time = "2026-03-25T10:57:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/9c/95/9fd860997685e64abe2d5a995ca2eb5004c0fb6d6585429612a7871548b9/nh3-0.3.4-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a", size = 1103526, upload-time = "2026-03-25T10:57:23.487Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0d/df545070614c1007f0109bb004230226c9000e7857c9785583ec25cda9d7/nh3-0.3.4-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3", size = 1068050, upload-time = "2026-03-25T10:57:24.624Z" }, - { url = "https://files.pythonhosted.org/packages/94/d5/17b016df52df052f714c53be71df26a1943551d9931e9383b92c998b88f8/nh3-0.3.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853", size = 1046037, upload-time = "2026-03-25T10:57:25.799Z" }, - { url = "https://files.pythonhosted.org/packages/51/39/49f737907e6ab2b4ca71855d3bd63dd7958862e9c8b94fb4e5b18ccf6988/nh3-0.3.4-cp38-abi3-win32.whl", hash = "sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e", size = 609542, upload-time = "2026-03-25T10:57:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl", hash = "sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75", size = 624244, upload-time = "2026-03-25T10:57:28.302Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/37695d6b0168f6714b5c492331636a9e6123d6ec22d25876c68d06eab1b8/nh3-0.3.4-cp38-abi3-win_arm64.whl", hash = "sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9", size = 616649, upload-time = "2026-03-25T10:57:29.623Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5e/c400663d14be2216bc084ed2befc871b7b12563f85d40904f2a4bf0dd2b7/nh3-0.3.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f", size = 1417991 }, + { url = "https://files.pythonhosted.org/packages/36/f5/109526f5002ec41322ac8cafd50f0f154bae0c26b9607c0fcb708bdca8ec/nh3-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f", size = 790566 }, + { url = "https://files.pythonhosted.org/packages/7b/66/38950f2b4b316ffd82ee51ed8f9143d1f56fdd620312cacc91613b77b3e7/nh3-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df", size = 837538 }, + { url = "https://files.pythonhosted.org/packages/d8/9f/9d6da970e9524fe360ea02a2082856390c2c8ba540409d1be6e5851887b3/nh3-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8", size = 1012154 }, + { url = "https://files.pythonhosted.org/packages/54/92/7c85c33c241e9dd51dda115bd3f765e940446588cdaaca62ef8edffe675f/nh3-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af", size = 1092516 }, + { url = "https://files.pythonhosted.org/packages/16/0f/597842bdb2890999a3faa2f3fcb02db8aa6ad09320d3d843ff6d0a1f737b/nh3-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5", size = 1053793 }, + { url = "https://files.pythonhosted.org/packages/7d/32/669da65147bc10746d2e1d7a8a3dbfbffe0315f419e74b559e2ee3471a01/nh3-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89", size = 1035975 }, + { url = "https://files.pythonhosted.org/packages/a1/7e/9e97a8b3c5161c79b4bf21cc54e9334860a52cc54ede15bf2239ef494b73/nh3-0.3.4-cp314-cp314t-win32.whl", hash = "sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b", size = 600419 }, + { url = "https://files.pythonhosted.org/packages/e0/c7/6849d8d4295d3997d148eacb2d4b1c9faada4895ee3c1b1e12e72f4611e2/nh3-0.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1", size = 613342 }, + { url = "https://files.pythonhosted.org/packages/8b/0e/14a3f510f36c20b922c123a2730f071f938d006fb513aacfd46d6cbc03a7/nh3-0.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e", size = 607025 }, + { url = "https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49", size = 1439519 }, + { url = "https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0", size = 833798 }, + { url = "https://files.pythonhosted.org/packages/6b/19/9487790780b8c94eacca37866c1270b747a4af8e244d43b3b550fddbbf62/nh3-0.3.4-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44", size = 820414 }, + { url = "https://files.pythonhosted.org/packages/6b/b4/c6a340dd321d20b1e4a663307032741da045685c87403926c43656f6f5ec/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21", size = 1061531 }, + { url = "https://files.pythonhosted.org/packages/c4/49/f6b4b474e0032e4bcbb7174b44e4cf6915670e09c62421deb06ccfcb88b8/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382", size = 1021889 }, + { url = "https://files.pythonhosted.org/packages/43/da/e52a6941746d1f974752af3fc8591f1dbcdcf7fd8c726c7d99f444ba820e/nh3-0.3.4-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4", size = 912965 }, + { url = "https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304", size = 804975 }, + { url = "https://files.pythonhosted.org/packages/a9/56/b1275aa2c6510191eed76178da4626b0900402439cb9f27d6b9bf7c6d5e9/nh3-0.3.4-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0", size = 833400 }, + { url = "https://files.pythonhosted.org/packages/7c/a5/5d574ffa3c6e49a5364d1b25ebad165501c055340056671493beb467a15e/nh3-0.3.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5", size = 854277 }, + { url = "https://files.pythonhosted.org/packages/79/36/8aeb2ab21517cefa212db109e41024e02650716cb42bf293d0a88437a92d/nh3-0.3.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003", size = 1022021 }, + { url = "https://files.pythonhosted.org/packages/9c/95/9fd860997685e64abe2d5a995ca2eb5004c0fb6d6585429612a7871548b9/nh3-0.3.4-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a", size = 1103526 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/df545070614c1007f0109bb004230226c9000e7857c9785583ec25cda9d7/nh3-0.3.4-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3", size = 1068050 }, + { url = "https://files.pythonhosted.org/packages/94/d5/17b016df52df052f714c53be71df26a1943551d9931e9383b92c998b88f8/nh3-0.3.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853", size = 1046037 }, + { url = "https://files.pythonhosted.org/packages/51/39/49f737907e6ab2b4ca71855d3bd63dd7958862e9c8b94fb4e5b18ccf6988/nh3-0.3.4-cp38-abi3-win32.whl", hash = "sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e", size = 609542 }, + { url = "https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl", hash = "sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75", size = 624244 }, + { url = "https://files.pythonhosted.org/packages/44/0c/37695d6b0168f6714b5c492331636a9e6123d6ec22d25876c68d06eab1b8/nh3-0.3.4-cp38-abi3-win_arm64.whl", hash = "sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9", size = 616649 }, ] [[package]] @@ -4723,9 +4715,9 @@ dependencies = [ { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, + { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087 }, ] [[package]] @@ -4735,9 +4727,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/39/60afcbc0148c3dafaaefe851ae3f058077db49d66288dfb218a11a57b997/notion_client-3.0.0.tar.gz", hash = "sha256:05c4d2b4fa3491dc0de21c9c826277202ea8b8714077ee7f51a6e1a09ab23d0f", size = 31357, upload-time = "2026-02-16T11:15:48.024Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/39/60afcbc0148c3dafaaefe851ae3f058077db49d66288dfb218a11a57b997/notion_client-3.0.0.tar.gz", hash = "sha256:05c4d2b4fa3491dc0de21c9c826277202ea8b8714077ee7f51a6e1a09ab23d0f", size = 31357 } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/ce/6b03f9aedd2edfcc28e23ced5c2582d543f6ddbb2be5c570533f02890b27/notion_client-3.0.0-py2.py3-none-any.whl", hash = "sha256:177fc3d2ace7e8ef69cf96f46269e8a66071c2c7c526194bf06ce7925853e759", size = 18746, upload-time = "2026-02-16T11:15:46.602Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ce/6b03f9aedd2edfcc28e23ced5c2582d543f6ddbb2be5c570533f02890b27/notion_client-3.0.0-py2.py3-none-any.whl", hash = "sha256:177fc3d2ace7e8ef69cf96f46269e8a66071c2c7c526194bf06ce7925853e759", size = 18746 }, ] [[package]] @@ -4748,9 +4740,9 @@ dependencies = [ { name = "mistune" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/c1/7614cd3ce5df401209051808e3c59c4959b52641a2e6c86c64184d754a49/notion_markdown-0.7.0.tar.gz", hash = "sha256:941b3a5b04eee991d971114a9edc09b0935c6ab79f987a6f4999774d643fe362", size = 42558, upload-time = "2026-02-13T05:48:21.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/c1/7614cd3ce5df401209051808e3c59c4959b52641a2e6c86c64184d754a49/notion_markdown-0.7.0.tar.gz", hash = "sha256:941b3a5b04eee991d971114a9edc09b0935c6ab79f987a6f4999774d643fe362", size = 42558 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/6e0f302211bd615b16bd8431d2855225b9b2901b84b6d6785c730a9a4a91/notion_markdown-0.7.0-py3-none-any.whl", hash = "sha256:ae0c38ec73e114f8b6a367bd81eca0640b2af366ac2ea117028080c713ea6ea9", size = 23355, upload-time = "2026-02-13T05:48:20.369Z" }, + { url = "https://files.pythonhosted.org/packages/5d/85/6e0f302211bd615b16bd8431d2855225b9b2901b84b6d6785c730a9a4a91/notion_markdown-0.7.0-py3-none-any.whl", hash = "sha256:ae0c38ec73e114f8b6a367bd81eca0640b2af366ac2ea117028080c713ea6ea9", size = 23355 }, ] [[package]] @@ -4760,9 +4752,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docopt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213, upload-time = "2024-12-17T20:17:10.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525, upload-time = "2024-12-17T20:17:06.074Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525 }, ] [[package]] @@ -4773,81 +4765,81 @@ dependencies = [ { name = "llvmlite" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z" }, - { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z" }, - { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z" }, - { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z" }, - { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590, upload-time = "2026-02-18T18:41:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163, upload-time = "2026-02-18T18:41:15.377Z" }, - { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172, upload-time = "2026-02-18T18:41:17.281Z" }, - { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700, upload-time = "2026-02-18T18:41:19.277Z" }, + { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418 }, + { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087 }, + { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309 }, + { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740 }, + { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392 }, + { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245 }, + { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328 }, + { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668 }, + { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590 }, + { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163 }, + { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172 }, + { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700 }, ] [[package]] name = "numpy" version = "2.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, - { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, - { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, - { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, - { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, - { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, - { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, - { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, - { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, - { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, - { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, - { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, - { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628 }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872 }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489 }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814 }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601 }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358 }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135 }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816 }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132 }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144 }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364 }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297 }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853 }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435 }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347 }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626 }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916 }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824 }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581 }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618 }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824 }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218 }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570 }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113 }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370 }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499 }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164 }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544 }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290 }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814 }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673 }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907 }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563 }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161 }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738 }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618 }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676 }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492 }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789 }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941 }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503 }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915 }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875 }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225 }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769 }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461 }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809 }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242 }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660 }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384 }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547 }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645 }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454 }, ] [[package]] @@ -4855,8 +4847,8 @@ name = "nvidia-cublas" version = "13.1.0.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, - { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226 }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236 }, ] [[package]] @@ -4864,8 +4856,8 @@ name = "nvidia-cuda-cupti" version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, - { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827 }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597 }, ] [[package]] @@ -4873,8 +4865,8 @@ name = "nvidia-cuda-nvrtc" version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, - { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200 }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449 }, ] [[package]] @@ -4882,8 +4874,8 @@ name = "nvidia-cuda-runtime" version = "13.0.96" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, - { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060 }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632 }, ] [[package]] @@ -4894,8 +4886,8 @@ dependencies = [ { name = "nvidia-cublas", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, - { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201 }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321 }, ] [[package]] @@ -4906,8 +4898,8 @@ dependencies = [ { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554 }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489 }, ] [[package]] @@ -4915,8 +4907,8 @@ name = "nvidia-cufile" version = "1.15.1.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, - { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672 }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992 }, ] [[package]] @@ -4924,8 +4916,8 @@ name = "nvidia-curand" version = "10.4.0.35" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106 }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258 }, ] [[package]] @@ -4938,8 +4930,8 @@ dependencies = [ { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, - { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760 }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980 }, ] [[package]] @@ -4950,8 +4942,8 @@ dependencies = [ { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, - { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568 }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937 }, ] [[package]] @@ -4959,8 +4951,8 @@ name = "nvidia-cusparselt-cu13" version = "0.8.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, - { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277 }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119 }, ] [[package]] @@ -4968,8 +4960,8 @@ name = "nvidia-nccl-cu13" version = "2.28.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677 }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177 }, ] [[package]] @@ -4977,8 +4969,8 @@ name = "nvidia-nvjitlink" version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933 }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748 }, ] [[package]] @@ -4986,8 +4978,8 @@ name = "nvidia-nvshmem-cu13" version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, - { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947 }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546 }, ] [[package]] @@ -4995,17 +4987,17 @@ name = "nvidia-nvtx" version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, - { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047 }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878 }, ] [[package]] name = "oauthlib" version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065 }, ] [[package]] @@ -5015,46 +5007,46 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/9ec984edd0f3b72226adfaa19b1c61b15823b35b52f311ca4af36d009d15/obstore-0.8.2.tar.gz", hash = "sha256:a467bc4e97169e2ba749981b4fd0936015428d9b8f3fb83a5528536b1b6f377f", size = 168852, upload-time = "2025-09-16T15:34:55.786Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/9ec984edd0f3b72226adfaa19b1c61b15823b35b52f311ca4af36d009d15/obstore-0.8.2.tar.gz", hash = "sha256:a467bc4e97169e2ba749981b4fd0936015428d9b8f3fb83a5528536b1b6f377f", size = 168852 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/dc/60fefbb5736e69eab56657bca04ca64dc07fdeccb3814164a31b62ad066b/obstore-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bb70ce297a47392b1d9a3e310f18d59cd5ebbb9453428210fef02ed60e4d75d1", size = 3612955, upload-time = "2025-09-16T15:33:29.527Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/844e8f382e5a12b8a3796a05d76a03e12c7aedc13d6900419e39207d7868/obstore-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1619bf618428abf1f607e0b219b2e230a966dcf697b717deccfa0983dd91f646", size = 3346564, upload-time = "2025-09-16T15:33:30.698Z" }, - { url = "https://files.pythonhosted.org/packages/89/73/8537f99e09a38a54a6a15ede907aa25d4da089f767a808f0b2edd9c03cec/obstore-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4605c3ed7c9515aeb4c619b5f7f2c9986ed4a79fe6045e536b5e59b804b1476", size = 3460809, upload-time = "2025-09-16T15:33:31.837Z" }, - { url = "https://files.pythonhosted.org/packages/b4/99/7714dec721e43f521d6325a82303a002cddad089437640f92542b84e9cc8/obstore-0.8.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce42670417876dd8668cbb8659e860e9725e5f26bbc86449fd259970e2dd9d18", size = 3692081, upload-time = "2025-09-16T15:33:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bd/4ac4175fe95a24c220a96021c25c432bcc0c0212f618be0737184eebbaad/obstore-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a3e893b2a06585f651c541c1972fe1e3bf999ae2a5fda052ee55eb7e6516f5", size = 3957466, upload-time = "2025-09-16T15:33:34.528Z" }, - { url = "https://files.pythonhosted.org/packages/4e/04/caa288fb735484fc5cb019bdf3d896eaccfae0ac4622e520d05692c46790/obstore-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08462b32f95a9948ed56ed63e88406e2e5a4cae1fde198f9682e0fb8487100ed", size = 3951293, upload-time = "2025-09-16T15:33:35.733Z" }, - { url = "https://files.pythonhosted.org/packages/44/2f/d380239da2d6a1fda82e17df5dae600a404e8a93a065784518ff8325d5f6/obstore-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0bf7763292a8fc47d01cd66e6f19002c5c6ad4b3ed4e6b2729f5e190fa8a0d", size = 3766199, upload-time = "2025-09-16T15:33:36.904Z" }, - { url = "https://files.pythonhosted.org/packages/28/41/d391be069d3da82969b54266948b2582aeca5dd735abeda4d63dba36e07b/obstore-0.8.2-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:bcd47f8126cb192cbe86942b8f73b1c45a651ce7e14c9a82c5641dfbf8be7603", size = 3529678, upload-time = "2025-09-16T15:33:38.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/4c/4862fdd1a3abde459ee8eea699b1797df638a460af235b18ca82c8fffb72/obstore-0.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57eda9fd8c757c3b4fe36cf3918d7e589cc1286591295cc10b34122fa36dd3fd", size = 3698079, upload-time = "2025-09-16T15:33:39.696Z" }, - { url = "https://files.pythonhosted.org/packages/68/ca/014e747bc53b570059c27e3565b2316fbe5c107d4134551f4cd3e24aa667/obstore-0.8.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ea44442aad8992166baa69f5069750979e4c5d9ffce772e61565945eea5774b9", size = 3687154, upload-time = "2025-09-16T15:33:40.92Z" }, - { url = "https://files.pythonhosted.org/packages/6f/89/6db5f8edd93028e5b8bfbeee15e6bd3e56f72106107d31cb208b57659de4/obstore-0.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41496a3ab8527402db4142aaaf0d42df9d7d354b13ba10d9c33e0e48dd49dd96", size = 3773444, upload-time = "2025-09-16T15:33:42.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/e5/c9e2cc540689c873beb61246e1615d6e38301e6a34dec424f5a5c63c1afd/obstore-0.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43da209803f052df96c7c3cbec512d310982efd2407e4a435632841a51143170", size = 3939315, upload-time = "2025-09-16T15:33:43.252Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c9/bb53280ca50103c1ffda373cdc9b0f835431060039c2897cbc87ddd92e42/obstore-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:1836f5dcd49f9f2950c75889ab5c51fb290d3ea93cdc39a514541e0be3af016e", size = 3978234, upload-time = "2025-09-16T15:33:44.393Z" }, - { url = "https://files.pythonhosted.org/packages/f0/5d/8c3316cc958d386d5e6ab03e9db9ddc27f8e2141cee4a6777ae5b92f3aac/obstore-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:212f033e53fe6e53d64957923c5c88949a400e9027f7038c705ec2e9038be563", size = 3612027, upload-time = "2025-09-16T15:33:45.6Z" }, - { url = "https://files.pythonhosted.org/packages/ea/4d/699359774ce6330130536d008bfc32827fab0c25a00238d015a5974a3d1d/obstore-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bee21fa4ba148d08fa90e47a96df11161661ed31e09c056a373cb2154b0f2852", size = 3344686, upload-time = "2025-09-16T15:33:47.185Z" }, - { url = "https://files.pythonhosted.org/packages/82/37/55437341f10512906e02fd9fa69a8a95ad3f2f6a916d3233fda01763d110/obstore-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c66594b59832ff1ced4c72575d9beb8b5f9b4e404ac1150a42bfb226617fd50", size = 3459860, upload-time = "2025-09-16T15:33:48.382Z" }, - { url = "https://files.pythonhosted.org/packages/7a/51/4245a616c94ee4851965e33f7a563ab4090cc81f52cc73227ff9ceca2e46/obstore-0.8.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:089f33af5c2fe132d00214a0c1f40601b28f23a38e24ef9f79fb0576f2730b74", size = 3691648, upload-time = "2025-09-16T15:33:49.524Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f1/4e2fb24171e3ca3641a4653f006be826e7e17634b11688a5190553b00b83/obstore-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87f658dfd340d5d9ea2d86a7c90d44da77a0db9e00c034367dca335735110cf", size = 3956867, upload-time = "2025-09-16T15:33:51.082Z" }, - { url = "https://files.pythonhosted.org/packages/42/f5/b703115361c798c9c1744e1e700d5908d904a8c2e2bd38bec759c9ffb469/obstore-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2e4fa92828c4fbc2d487f3da2d3588701a1b67d9f6ca3c97cc2afc912e9c63", size = 3950599, upload-time = "2025-09-16T15:33:52.173Z" }, - { url = "https://files.pythonhosted.org/packages/53/20/08c6dc0f20c1394e2324b9344838e4e7af770cdcb52c30757a475f50daeb/obstore-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab440e89c5c37a8ec230857dd65147d4b923e0cada33297135d05e0f937d696a", size = 3765865, upload-time = "2025-09-16T15:33:53.291Z" }, - { url = "https://files.pythonhosted.org/packages/77/20/77907765e29b2eba6bd8821872284d91170d7084f670855b2dfcb249ea14/obstore-0.8.2-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:b9beed107c5c9cd995d4a73263861fcfbc414d58773ed65c14f80eb18258a932", size = 3529807, upload-time = "2025-09-16T15:33:54.535Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f5/f629d39cc30d050f52b1bf927e4d65c1cc7d7ffbb8a635cd546b5c5219a0/obstore-0.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b75b4e7746292c785e31edcd5aadc8b758238372a19d4c5e394db5c305d7d175", size = 3693629, upload-time = "2025-09-16T15:33:56.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/ff/106763fd10f2a1cb47f2ef1162293c78ad52f4e73223d8d43fc6b755445d/obstore-0.8.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f33e6c366869d05ab0b7f12efe63269e631c5450d95d6b4ba4c5faf63f69de70", size = 3686176, upload-time = "2025-09-16T15:33:57.247Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0c/d2ccb6f32feeca906d5a7c4255340df5262af8838441ca06c9e4e37b67d5/obstore-0.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:12c885a9ce5ceb09d13cc186586c0c10b62597eff21b985f6ce8ff9dab963ad3", size = 3773081, upload-time = "2025-09-16T15:33:58.475Z" }, - { url = "https://files.pythonhosted.org/packages/fa/79/40d1cc504cefc89c9b3dd8874287f3fddc7d963a8748d6dffc5880222013/obstore-0.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4accc883b93349a81c9931e15dd318cc703b02bbef2805d964724c73d006d00e", size = 3938589, upload-time = "2025-09-16T15:33:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/916c6777222db3271e9fb3cf9a97ed92b3a9b3e465bdeec96de9ab809d53/obstore-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ec850adf9980e5788a826ccfd5819989724e2a2f712bfa3258e85966c8d9981e", size = 3977768, upload-time = "2025-09-16T15:34:01.25Z" }, - { url = "https://files.pythonhosted.org/packages/f1/61/66f8dc98bbf5613bbfe5bf21747b4c8091442977f4bd897945895ab7325c/obstore-0.8.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1431e40e9bb4773a261e51b192ea6489d0799b9d4d7dbdf175cdf813eb8c0503", size = 3623364, upload-time = "2025-09-16T15:34:02.957Z" }, - { url = "https://files.pythonhosted.org/packages/1a/66/6d527b3027e42f625c8fc816ac7d19b0d6228f95bfe7666e4d6b081d2348/obstore-0.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ddb39d4da303f50b959da000aa42734f6da7ac0cc0be2d5a7838b62c97055bb9", size = 3347764, upload-time = "2025-09-16T15:34:04.236Z" }, - { url = "https://files.pythonhosted.org/packages/0d/79/c00103302b620192ea447a948921ad3fed031ce3d19e989f038e1183f607/obstore-0.8.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e01f4e13783db453e17e005a4a3ceff09c41c262e44649ba169d253098c775e8", size = 3460981, upload-time = "2025-09-16T15:34:05.595Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d9/bfe4ed4b1aebc45b56644dd5b943cf8e1673505cccb352e66878a457e807/obstore-0.8.2-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0fc2d0bc17caff9b538564ddc26d7616f7e8b7c65b1a3c90b5048a8ad2e797", size = 3692711, upload-time = "2025-09-16T15:34:06.796Z" }, - { url = "https://files.pythonhosted.org/packages/13/47/cd6c2cbb18e1f40c77e7957a4a03d2d83f1859a2e876a408f1ece81cad4c/obstore-0.8.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e439d06c99a140348f046c9f598ee349cc2dcd9105c15540a4b231f9cc48bbae", size = 3958362, upload-time = "2025-09-16T15:34:08.277Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ea/5ee82bf23abd71c7d6a3f2d008197ae8f8f569d41314c26a8f75318245be/obstore-0.8.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e37d9046669fcc59522d0faf1d105fcbfd09c84cccaaa1e809227d8e030f32c", size = 3957082, upload-time = "2025-09-16T15:34:09.477Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ee/46650405e50fdaa8d95f30375491f9c91fac9517980e8a28a4a6af66927f/obstore-0.8.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2646fdcc4bbe92dc2bb5bcdff15574da1211f5806c002b66d514cee2a23c7cb8", size = 3775539, upload-time = "2025-09-16T15:34:10.726Z" }, - { url = "https://files.pythonhosted.org/packages/35/d6/348a7ebebe2ca3d94dfc75344ea19675ae45472823e372c1852844078307/obstore-0.8.2-cp314-cp314-manylinux_2_24_aarch64.whl", hash = "sha256:e31a7d37675056d93dfc244605089dee67f5bba30f37c88436623c8c5ad9ba9d", size = 3535048, upload-time = "2025-09-16T15:34:12.076Z" }, - { url = "https://files.pythonhosted.org/packages/41/07/b7a16cc0da91a4b902d47880ad24016abfe7880c63f7cdafda45d89a2f91/obstore-0.8.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:656313dd8170dde0f0cd471433283337a63912e8e790a121f7cc7639c83e3816", size = 3699035, upload-time = "2025-09-16T15:34:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/3269a3a58347e0b019742d888612c4b765293c9c75efa44e144b1e884c0d/obstore-0.8.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329038c9645d6d1741e77fe1a53e28a14b1a5c1461cfe4086082ad39ebabf981", size = 3687307, upload-time = "2025-09-16T15:34:14.501Z" }, - { url = "https://files.pythonhosted.org/packages/01/f9/4fd4819ad6a49d2f462a45be453561f4caebded0dc40112deeffc34b89b1/obstore-0.8.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1e4df99b369790c97c752d126b286dc86484ea49bff5782843a265221406566f", size = 3776076, upload-time = "2025-09-16T15:34:16.207Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/7c4f958fa0b9fc4778fb3d232e38b37db8c6b260f641022fbba48b049d7e/obstore-0.8.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9e1c65c65e20cc990414a8a9af88209b1bbc0dd9521b5f6b0293c60e19439bb7", size = 3947445, upload-time = "2025-09-16T15:34:17.423Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/60fefbb5736e69eab56657bca04ca64dc07fdeccb3814164a31b62ad066b/obstore-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bb70ce297a47392b1d9a3e310f18d59cd5ebbb9453428210fef02ed60e4d75d1", size = 3612955 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/844e8f382e5a12b8a3796a05d76a03e12c7aedc13d6900419e39207d7868/obstore-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1619bf618428abf1f607e0b219b2e230a966dcf697b717deccfa0983dd91f646", size = 3346564 }, + { url = "https://files.pythonhosted.org/packages/89/73/8537f99e09a38a54a6a15ede907aa25d4da089f767a808f0b2edd9c03cec/obstore-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4605c3ed7c9515aeb4c619b5f7f2c9986ed4a79fe6045e536b5e59b804b1476", size = 3460809 }, + { url = "https://files.pythonhosted.org/packages/b4/99/7714dec721e43f521d6325a82303a002cddad089437640f92542b84e9cc8/obstore-0.8.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce42670417876dd8668cbb8659e860e9725e5f26bbc86449fd259970e2dd9d18", size = 3692081 }, + { url = "https://files.pythonhosted.org/packages/ec/bd/4ac4175fe95a24c220a96021c25c432bcc0c0212f618be0737184eebbaad/obstore-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a3e893b2a06585f651c541c1972fe1e3bf999ae2a5fda052ee55eb7e6516f5", size = 3957466 }, + { url = "https://files.pythonhosted.org/packages/4e/04/caa288fb735484fc5cb019bdf3d896eaccfae0ac4622e520d05692c46790/obstore-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08462b32f95a9948ed56ed63e88406e2e5a4cae1fde198f9682e0fb8487100ed", size = 3951293 }, + { url = "https://files.pythonhosted.org/packages/44/2f/d380239da2d6a1fda82e17df5dae600a404e8a93a065784518ff8325d5f6/obstore-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0bf7763292a8fc47d01cd66e6f19002c5c6ad4b3ed4e6b2729f5e190fa8a0d", size = 3766199 }, + { url = "https://files.pythonhosted.org/packages/28/41/d391be069d3da82969b54266948b2582aeca5dd735abeda4d63dba36e07b/obstore-0.8.2-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:bcd47f8126cb192cbe86942b8f73b1c45a651ce7e14c9a82c5641dfbf8be7603", size = 3529678 }, + { url = "https://files.pythonhosted.org/packages/b9/4c/4862fdd1a3abde459ee8eea699b1797df638a460af235b18ca82c8fffb72/obstore-0.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57eda9fd8c757c3b4fe36cf3918d7e589cc1286591295cc10b34122fa36dd3fd", size = 3698079 }, + { url = "https://files.pythonhosted.org/packages/68/ca/014e747bc53b570059c27e3565b2316fbe5c107d4134551f4cd3e24aa667/obstore-0.8.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ea44442aad8992166baa69f5069750979e4c5d9ffce772e61565945eea5774b9", size = 3687154 }, + { url = "https://files.pythonhosted.org/packages/6f/89/6db5f8edd93028e5b8bfbeee15e6bd3e56f72106107d31cb208b57659de4/obstore-0.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41496a3ab8527402db4142aaaf0d42df9d7d354b13ba10d9c33e0e48dd49dd96", size = 3773444 }, + { url = "https://files.pythonhosted.org/packages/26/e5/c9e2cc540689c873beb61246e1615d6e38301e6a34dec424f5a5c63c1afd/obstore-0.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43da209803f052df96c7c3cbec512d310982efd2407e4a435632841a51143170", size = 3939315 }, + { url = "https://files.pythonhosted.org/packages/4d/c9/bb53280ca50103c1ffda373cdc9b0f835431060039c2897cbc87ddd92e42/obstore-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:1836f5dcd49f9f2950c75889ab5c51fb290d3ea93cdc39a514541e0be3af016e", size = 3978234 }, + { url = "https://files.pythonhosted.org/packages/f0/5d/8c3316cc958d386d5e6ab03e9db9ddc27f8e2141cee4a6777ae5b92f3aac/obstore-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:212f033e53fe6e53d64957923c5c88949a400e9027f7038c705ec2e9038be563", size = 3612027 }, + { url = "https://files.pythonhosted.org/packages/ea/4d/699359774ce6330130536d008bfc32827fab0c25a00238d015a5974a3d1d/obstore-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bee21fa4ba148d08fa90e47a96df11161661ed31e09c056a373cb2154b0f2852", size = 3344686 }, + { url = "https://files.pythonhosted.org/packages/82/37/55437341f10512906e02fd9fa69a8a95ad3f2f6a916d3233fda01763d110/obstore-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c66594b59832ff1ced4c72575d9beb8b5f9b4e404ac1150a42bfb226617fd50", size = 3459860 }, + { url = "https://files.pythonhosted.org/packages/7a/51/4245a616c94ee4851965e33f7a563ab4090cc81f52cc73227ff9ceca2e46/obstore-0.8.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:089f33af5c2fe132d00214a0c1f40601b28f23a38e24ef9f79fb0576f2730b74", size = 3691648 }, + { url = "https://files.pythonhosted.org/packages/4e/f1/4e2fb24171e3ca3641a4653f006be826e7e17634b11688a5190553b00b83/obstore-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87f658dfd340d5d9ea2d86a7c90d44da77a0db9e00c034367dca335735110cf", size = 3956867 }, + { url = "https://files.pythonhosted.org/packages/42/f5/b703115361c798c9c1744e1e700d5908d904a8c2e2bd38bec759c9ffb469/obstore-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2e4fa92828c4fbc2d487f3da2d3588701a1b67d9f6ca3c97cc2afc912e9c63", size = 3950599 }, + { url = "https://files.pythonhosted.org/packages/53/20/08c6dc0f20c1394e2324b9344838e4e7af770cdcb52c30757a475f50daeb/obstore-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab440e89c5c37a8ec230857dd65147d4b923e0cada33297135d05e0f937d696a", size = 3765865 }, + { url = "https://files.pythonhosted.org/packages/77/20/77907765e29b2eba6bd8821872284d91170d7084f670855b2dfcb249ea14/obstore-0.8.2-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:b9beed107c5c9cd995d4a73263861fcfbc414d58773ed65c14f80eb18258a932", size = 3529807 }, + { url = "https://files.pythonhosted.org/packages/a5/f5/f629d39cc30d050f52b1bf927e4d65c1cc7d7ffbb8a635cd546b5c5219a0/obstore-0.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b75b4e7746292c785e31edcd5aadc8b758238372a19d4c5e394db5c305d7d175", size = 3693629 }, + { url = "https://files.pythonhosted.org/packages/30/ff/106763fd10f2a1cb47f2ef1162293c78ad52f4e73223d8d43fc6b755445d/obstore-0.8.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f33e6c366869d05ab0b7f12efe63269e631c5450d95d6b4ba4c5faf63f69de70", size = 3686176 }, + { url = "https://files.pythonhosted.org/packages/ce/0c/d2ccb6f32feeca906d5a7c4255340df5262af8838441ca06c9e4e37b67d5/obstore-0.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:12c885a9ce5ceb09d13cc186586c0c10b62597eff21b985f6ce8ff9dab963ad3", size = 3773081 }, + { url = "https://files.pythonhosted.org/packages/fa/79/40d1cc504cefc89c9b3dd8874287f3fddc7d963a8748d6dffc5880222013/obstore-0.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4accc883b93349a81c9931e15dd318cc703b02bbef2805d964724c73d006d00e", size = 3938589 }, + { url = "https://files.pythonhosted.org/packages/14/dd/916c6777222db3271e9fb3cf9a97ed92b3a9b3e465bdeec96de9ab809d53/obstore-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ec850adf9980e5788a826ccfd5819989724e2a2f712bfa3258e85966c8d9981e", size = 3977768 }, + { url = "https://files.pythonhosted.org/packages/f1/61/66f8dc98bbf5613bbfe5bf21747b4c8091442977f4bd897945895ab7325c/obstore-0.8.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1431e40e9bb4773a261e51b192ea6489d0799b9d4d7dbdf175cdf813eb8c0503", size = 3623364 }, + { url = "https://files.pythonhosted.org/packages/1a/66/6d527b3027e42f625c8fc816ac7d19b0d6228f95bfe7666e4d6b081d2348/obstore-0.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ddb39d4da303f50b959da000aa42734f6da7ac0cc0be2d5a7838b62c97055bb9", size = 3347764 }, + { url = "https://files.pythonhosted.org/packages/0d/79/c00103302b620192ea447a948921ad3fed031ce3d19e989f038e1183f607/obstore-0.8.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e01f4e13783db453e17e005a4a3ceff09c41c262e44649ba169d253098c775e8", size = 3460981 }, + { url = "https://files.pythonhosted.org/packages/3d/d9/bfe4ed4b1aebc45b56644dd5b943cf8e1673505cccb352e66878a457e807/obstore-0.8.2-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0fc2d0bc17caff9b538564ddc26d7616f7e8b7c65b1a3c90b5048a8ad2e797", size = 3692711 }, + { url = "https://files.pythonhosted.org/packages/13/47/cd6c2cbb18e1f40c77e7957a4a03d2d83f1859a2e876a408f1ece81cad4c/obstore-0.8.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e439d06c99a140348f046c9f598ee349cc2dcd9105c15540a4b231f9cc48bbae", size = 3958362 }, + { url = "https://files.pythonhosted.org/packages/3d/ea/5ee82bf23abd71c7d6a3f2d008197ae8f8f569d41314c26a8f75318245be/obstore-0.8.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e37d9046669fcc59522d0faf1d105fcbfd09c84cccaaa1e809227d8e030f32c", size = 3957082 }, + { url = "https://files.pythonhosted.org/packages/cb/ee/46650405e50fdaa8d95f30375491f9c91fac9517980e8a28a4a6af66927f/obstore-0.8.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2646fdcc4bbe92dc2bb5bcdff15574da1211f5806c002b66d514cee2a23c7cb8", size = 3775539 }, + { url = "https://files.pythonhosted.org/packages/35/d6/348a7ebebe2ca3d94dfc75344ea19675ae45472823e372c1852844078307/obstore-0.8.2-cp314-cp314-manylinux_2_24_aarch64.whl", hash = "sha256:e31a7d37675056d93dfc244605089dee67f5bba30f37c88436623c8c5ad9ba9d", size = 3535048 }, + { url = "https://files.pythonhosted.org/packages/41/07/b7a16cc0da91a4b902d47880ad24016abfe7880c63f7cdafda45d89a2f91/obstore-0.8.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:656313dd8170dde0f0cd471433283337a63912e8e790a121f7cc7639c83e3816", size = 3699035 }, + { url = "https://files.pythonhosted.org/packages/7f/74/3269a3a58347e0b019742d888612c4b765293c9c75efa44e144b1e884c0d/obstore-0.8.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329038c9645d6d1741e77fe1a53e28a14b1a5c1461cfe4086082ad39ebabf981", size = 3687307 }, + { url = "https://files.pythonhosted.org/packages/01/f9/4fd4819ad6a49d2f462a45be453561f4caebded0dc40112deeffc34b89b1/obstore-0.8.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1e4df99b369790c97c752d126b286dc86484ea49bff5782843a265221406566f", size = 3776076 }, + { url = "https://files.pythonhosted.org/packages/14/dd/7c4f958fa0b9fc4778fb3d232e38b37db8c6b260f641022fbba48b049d7e/obstore-0.8.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9e1c65c65e20cc990414a8a9af88209b1bbc0dd9521b5f6b0293c60e19439bb7", size = 3947445 }, ] [[package]] @@ -5066,18 +5058,18 @@ dependencies = [ { name = "pillow", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-vision", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/07/3e15ab404f75875c5e48c47163300eb90b7409044d8711fc3aaf52503f2e/ocrmac-1.0.1.tar.gz", hash = "sha256:507fe5e4cbd67b2d03f6729a52bbc11f9d0b58241134eb958a5daafd4b9d93d9", size = 1454317, upload-time = "2026-01-08T16:44:26.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/07/3e15ab404f75875c5e48c47163300eb90b7409044d8711fc3aaf52503f2e/ocrmac-1.0.1.tar.gz", hash = "sha256:507fe5e4cbd67b2d03f6729a52bbc11f9d0b58241134eb958a5daafd4b9d93d9", size = 1454317 } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/15/7cc16507a2aca927abe395f1c545f17ae76b1f8ed44f43ebe4e8670ee203/ocrmac-1.0.1-py3-none-any.whl", hash = "sha256:1cef25426f7ae6bbd57fe3dc5553b25461ae8ad0d2b428a9bbadbf5907349024", size = 9955, upload-time = "2026-01-08T16:44:25.555Z" }, + { url = "https://files.pythonhosted.org/packages/37/15/7cc16507a2aca927abe395f1c545f17ae76b1f8ed44f43ebe4e8670ee203/ocrmac-1.0.1-py3-none-any.whl", hash = "sha256:1cef25426f7ae6bbd57fe3dc5553b25461ae8ad0d2b428a9bbadbf5907349024", size = 9955 }, ] [[package]] name = "olefile" version = "0.47" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240, upload-time = "2023-12-01T16:22:53.025Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565, upload-time = "2023-12-01T16:22:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 }, ] [[package]] @@ -5088,9 +5080,9 @@ dependencies = [ { name = "antlr4-python3-runtime" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500 }, ] [[package]] @@ -5103,19 +5095,19 @@ dependencies = [ { name = "protobuf", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, - { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, - { url = "https://files.pythonhosted.org/packages/ea/bb/715fad292b255664f0e603f1b2ef7bf2b386281775f37406beb99fa05957/onnx-1.20.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:29197b768f5acdd1568ddeb0a376407a2817844f6ac1ef8c8dd2d974c9ab27c3", size = 17912296, upload-time = "2026-01-10T01:39:48.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c3/541af12c3d45e159a94ee701100ba9e94b7bd8b7a8ac5ca6838569f894f8/onnx-1.20.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f0371aa67f51917a09cc829ada0f9a79a58f833449e03d748f7f7f53787c43c", size = 17416925, upload-time = "2026-01-10T01:39:50.82Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/d5660a7d2ddf14f531ca66d409239f543bb290277c3f14f4b4b78e32efa3/onnx-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be1e5522200b203b34327b2cf132ddec20ab063469476e1f5b02bb7bd259a489", size = 17515602, upload-time = "2026-01-10T01:39:54.132Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b4/47225ab2a92562eff87ba9a1a028e3535d659a7157d7cde659003998b8e3/onnx-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:15c815313bbc4b2fdc7e4daeb6e26b6012012adc4d850f4e3b09ed327a7ea92a", size = 16395729, upload-time = "2026-01-10T01:39:57.577Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7d/1bbe626ff6b192c844d3ad34356840cc60fca02e2dea0db95e01645758b1/onnx-1.20.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb335d7bcf9abac82a0d6a0fda0363531ae0b22cfd0fc6304bff32ee29905def", size = 16348968, upload-time = "2026-01-10T01:40:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547 }, + { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021 }, + { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450 }, + { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216 }, + { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207 }, + { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155 }, + { url = "https://files.pythonhosted.org/packages/ea/bb/715fad292b255664f0e603f1b2ef7bf2b386281775f37406beb99fa05957/onnx-1.20.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:29197b768f5acdd1568ddeb0a376407a2817844f6ac1ef8c8dd2d974c9ab27c3", size = 17912296 }, + { url = "https://files.pythonhosted.org/packages/2d/c3/541af12c3d45e159a94ee701100ba9e94b7bd8b7a8ac5ca6838569f894f8/onnx-1.20.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f0371aa67f51917a09cc829ada0f9a79a58f833449e03d748f7f7f53787c43c", size = 17416925 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/d5660a7d2ddf14f531ca66d409239f543bb290277c3f14f4b4b78e32efa3/onnx-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be1e5522200b203b34327b2cf132ddec20ab063469476e1f5b02bb7bd259a489", size = 17515602 }, + { url = "https://files.pythonhosted.org/packages/9c/b4/47225ab2a92562eff87ba9a1a028e3535d659a7157d7cde659003998b8e3/onnx-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:15c815313bbc4b2fdc7e4daeb6e26b6012012adc4d850f4e3b09ed327a7ea92a", size = 16395729 }, + { url = "https://files.pythonhosted.org/packages/aa/7d/1bbe626ff6b192c844d3ad34356840cc60fca02e2dea0db95e01645758b1/onnx-1.20.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb335d7bcf9abac82a0d6a0fda0363531ae0b22cfd0fc6304bff32ee29905def", size = 16348968 }, ] [[package]] @@ -5130,25 +5122,25 @@ dependencies = [ { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875, upload-time = "2026-03-17T22:05:51.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485, upload-time = "2026-03-17T22:03:32.182Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912, upload-time = "2026-03-17T22:04:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856, upload-time = "2026-03-17T22:05:41.224Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275, upload-time = "2026-03-17T22:05:31.132Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f0/8a21ec0a97e40abb7d8da1e8b20fb9e1af509cc6d191f6faa75f73622fb2/onnxruntime-1.24.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0", size = 17341922, upload-time = "2026-03-17T22:03:56.364Z" }, - { url = "https://files.pythonhosted.org/packages/8b/25/d7908de8e08cee9abfa15b8aa82349b79733ae5865162a3609c11598805d/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13", size = 15172290, upload-time = "2026-03-17T22:03:37.124Z" }, - { url = "https://files.pythonhosted.org/packages/7f/72/105ec27a78c5aa0154a7c0cd8c41c19a97799c3b12fc30392928997e3be3/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f", size = 17244738, upload-time = "2026-03-17T22:04:40.625Z" }, - { url = "https://files.pythonhosted.org/packages/05/fb/a592736d968c2f58e12de4d52088dda8e0e724b26ad5c0487263adb45875/onnxruntime-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93", size = 12597435, upload-time = "2026-03-17T22:05:43.826Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/ae2479e9841b64bd2eb44f8a64756c62593f896514369a11243b1b86ca5c/onnxruntime-1.24.4-cp313-cp313-win_arm64.whl", hash = "sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19", size = 12269852, upload-time = "2026-03-17T22:05:33.353Z" }, - { url = "https://files.pythonhosted.org/packages/b4/af/a479a536c4398ffaf49fbbe755f45d5b8726bdb4335ab31b537f3d7149b8/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee", size = 15176861, upload-time = "2026-03-17T22:03:40.143Z" }, - { url = "https://files.pythonhosted.org/packages/be/13/19f5da70c346a76037da2c2851ecbf1266e61d7f0dcdb887c667210d4608/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36", size = 17247454, upload-time = "2026-03-17T22:04:46.643Z" }, - { url = "https://files.pythonhosted.org/packages/89/db/b30dbbd6037847b205ab75d962bc349bf1e46d02a65b30d7047a6893ffd6/onnxruntime-1.24.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4", size = 17343300, upload-time = "2026-03-17T22:03:59.223Z" }, - { url = "https://files.pythonhosted.org/packages/61/88/1746c0e7959961475b84c776d35601a21d445f463c93b1433a409ec3e188/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1", size = 15175936, upload-time = "2026-03-17T22:03:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ba/4699cde04a52cece66cbebc85bd8335a0d3b9ad485abc9a2e15946a1349d/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177", size = 17246432, upload-time = "2026-03-17T22:04:49.58Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/4590910841bb28bd3b4b388a9efbedf4e2d2cca99ddf0c863642b4e87814/onnxruntime-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858", size = 12903276, upload-time = "2026-03-17T22:05:46.349Z" }, - { url = "https://files.pythonhosted.org/packages/7f/6f/60e2c0acea1e1ac09b3e794b5a19c166eebf91c0b860b3e6db8e74983fda/onnxruntime-1.24.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d", size = 12594365, upload-time = "2026-03-17T22:05:35.795Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/0c05d10f8f6c40fe0912ebec0d5a33884aaa2af2053507e864dab0883208/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661", size = 15176889, upload-time = "2026-03-17T22:03:48.021Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1d/1666dc64e78d8587d168fec4e3b7922b92eb286a2ddeebcf6acb55c7dc82/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731", size = 17247021, upload-time = "2026-03-17T22:04:52.377Z" }, + { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875 }, + { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912 }, + { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856 }, + { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275 }, + { url = "https://files.pythonhosted.org/packages/e9/f0/8a21ec0a97e40abb7d8da1e8b20fb9e1af509cc6d191f6faa75f73622fb2/onnxruntime-1.24.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0", size = 17341922 }, + { url = "https://files.pythonhosted.org/packages/8b/25/d7908de8e08cee9abfa15b8aa82349b79733ae5865162a3609c11598805d/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13", size = 15172290 }, + { url = "https://files.pythonhosted.org/packages/7f/72/105ec27a78c5aa0154a7c0cd8c41c19a97799c3b12fc30392928997e3be3/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f", size = 17244738 }, + { url = "https://files.pythonhosted.org/packages/05/fb/a592736d968c2f58e12de4d52088dda8e0e724b26ad5c0487263adb45875/onnxruntime-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93", size = 12597435 }, + { url = "https://files.pythonhosted.org/packages/ad/04/ae2479e9841b64bd2eb44f8a64756c62593f896514369a11243b1b86ca5c/onnxruntime-1.24.4-cp313-cp313-win_arm64.whl", hash = "sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19", size = 12269852 }, + { url = "https://files.pythonhosted.org/packages/b4/af/a479a536c4398ffaf49fbbe755f45d5b8726bdb4335ab31b537f3d7149b8/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee", size = 15176861 }, + { url = "https://files.pythonhosted.org/packages/be/13/19f5da70c346a76037da2c2851ecbf1266e61d7f0dcdb887c667210d4608/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36", size = 17247454 }, + { url = "https://files.pythonhosted.org/packages/89/db/b30dbbd6037847b205ab75d962bc349bf1e46d02a65b30d7047a6893ffd6/onnxruntime-1.24.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4", size = 17343300 }, + { url = "https://files.pythonhosted.org/packages/61/88/1746c0e7959961475b84c776d35601a21d445f463c93b1433a409ec3e188/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1", size = 15175936 }, + { url = "https://files.pythonhosted.org/packages/5f/ba/4699cde04a52cece66cbebc85bd8335a0d3b9ad485abc9a2e15946a1349d/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177", size = 17246432 }, + { url = "https://files.pythonhosted.org/packages/ef/60/4590910841bb28bd3b4b388a9efbedf4e2d2cca99ddf0c863642b4e87814/onnxruntime-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858", size = 12903276 }, + { url = "https://files.pythonhosted.org/packages/7f/6f/60e2c0acea1e1ac09b3e794b5a19c166eebf91c0b860b3e6db8e74983fda/onnxruntime-1.24.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d", size = 12594365 }, + { url = "https://files.pythonhosted.org/packages/cf/68/0c05d10f8f6c40fe0912ebec0d5a33884aaa2af2053507e864dab0883208/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661", size = 15176889 }, + { url = "https://files.pythonhosted.org/packages/6c/1d/1666dc64e78d8587d168fec4e3b7922b92eb286a2ddeebcf6acb55c7dc82/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731", size = 17247021 }, ] [[package]] @@ -5165,9 +5157,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122 }, ] [[package]] @@ -5178,14 +5170,14 @@ dependencies = [ { name = "numpy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" }, - { url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781, upload-time = "2026-02-05T07:01:41.379Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527, upload-time = "2026-02-05T06:59:11.24Z" }, - { url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872, upload-time = "2026-02-05T06:59:19.162Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208, upload-time = "2026-02-05T06:59:15.419Z" }, - { url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042, upload-time = "2026-02-05T06:59:23.389Z" }, - { url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638, upload-time = "2026-02-05T07:02:14.98Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062, upload-time = "2026-02-05T07:02:12.724Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052 }, + { url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781 }, + { url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527 }, + { url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872 }, + { url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208 }, + { url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042 }, + { url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638 }, + { url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062 }, ] [[package]] @@ -5195,9 +5187,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "et-xmlfile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, ] [[package]] @@ -5208,9 +5200,9 @@ dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676 }, ] [[package]] @@ -5221,9 +5213,9 @@ dependencies = [ { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/37/b6708e0eff5c5fb9aba2e0ea09f7f3bcbfd12a592d2a780241b5f6014df7/opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a", size = 6152, upload-time = "2026-03-04T14:17:23.235Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/37/b6708e0eff5c5fb9aba2e0ea09f7f3bcbfd12a592d2a780241b5f6014df7/opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a", size = 6152 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/fc/aea77c28d9f3ffef2fdafdc3f4a235aee4091d262ddabd25882f47ce5c5f/opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d", size = 7023, upload-time = "2026-03-04T14:17:03.612Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fc/aea77c28d9f3ffef2fdafdc3f4a235aee4091d262ddabd25882f47ce5c5f/opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d", size = 7023 }, ] [[package]] @@ -5233,9 +5225,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369 }, ] [[package]] @@ -5251,9 +5243,9 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/7f/b9e60435cfcc7590fa87436edad6822240dddbc184643a2a005301cc31f4/opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740", size = 25759, upload-time = "2026-03-04T14:17:24.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/7f/b9e60435cfcc7590fa87436edad6822240dddbc184643a2a005301cc31f4/opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740", size = 25759 } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/6f/7ee0980afcbdcd2d40362da16f7f9796bd083bf7f0b8e038abfbc0300f5d/opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52", size = 20304, upload-time = "2026-03-04T14:17:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/96/6f/7ee0980afcbdcd2d40362da16f7f9796bd083bf7f0b8e038abfbc0300f5d/opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52", size = 20304 }, ] [[package]] @@ -5269,9 +5261,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960 }, ] [[package]] @@ -5284,9 +5276,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448 }, ] [[package]] @@ -5300,9 +5292,9 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/24fed4de661de107f2426b28bbd87b51eaab28a2339b62f269a36ae24505/opentelemetry_instrumentation_aiohttp_client-0.61b0.tar.gz", hash = "sha256:c53ab3b88efcb7ce98c1129cc0389f0a1f214eb3675269b6c157770adcf47877", size = 19292, upload-time = "2026-03-04T14:20:18.408Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/24fed4de661de107f2426b28bbd87b51eaab28a2339b62f269a36ae24505/opentelemetry_instrumentation_aiohttp_client-0.61b0.tar.gz", hash = "sha256:c53ab3b88efcb7ce98c1129cc0389f0a1f214eb3675269b6c157770adcf47877", size = 19292 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/f3/1edc42716521a3f754ac32ffb908f102e0f131f8e43fcd9ab29cab286723/opentelemetry_instrumentation_aiohttp_client-0.61b0-py3-none-any.whl", hash = "sha256:09bc47514c162507b357366ce15578743fd6305078cf7d872db1c99c13fa6972", size = 14534, upload-time = "2026-03-04T14:19:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/1edc42716521a3f754ac32ffb908f102e0f131f8e43fcd9ab29cab286723/opentelemetry_instrumentation_aiohttp_client-0.61b0-py3-none-any.whl", hash = "sha256:09bc47514c162507b357366ce15578743fd6305078cf7d872db1c99c13fa6972", size = 14534 }, ] [[package]] @@ -5316,9 +5308,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/3e/143cf5c034e58037307e6a24f06e0dd64b2c49ae60a965fc580027581931/opentelemetry_instrumentation_asgi-0.61b0.tar.gz", hash = "sha256:9d08e127244361dc33976d39dd4ca8f128b5aa5a7ae425208400a80a095019b5", size = 26691, upload-time = "2026-03-04T14:20:21.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/3e/143cf5c034e58037307e6a24f06e0dd64b2c49ae60a965fc580027581931/opentelemetry_instrumentation_asgi-0.61b0.tar.gz", hash = "sha256:9d08e127244361dc33976d39dd4ca8f128b5aa5a7ae425208400a80a095019b5", size = 26691 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/78/154470cf9d741a7487fbb5067357b87386475bbb77948a6707cae982e158/opentelemetry_instrumentation_asgi-0.61b0-py3-none-any.whl", hash = "sha256:e4b3ce6b66074e525e717efff20745434e5efd5d9df6557710856fba356da7a4", size = 16980, upload-time = "2026-03-04T14:19:10.894Z" }, + { url = "https://files.pythonhosted.org/packages/19/78/154470cf9d741a7487fbb5067357b87386475bbb77948a6707cae982e158/opentelemetry_instrumentation_asgi-0.61b0-py3-none-any.whl", hash = "sha256:e4b3ce6b66074e525e717efff20745434e5efd5d9df6557710856fba356da7a4", size = 16980 }, ] [[package]] @@ -5330,9 +5322,9 @@ dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/43/e79108a804d16b1dc8ff28edd0e94ac393cf6359a5adcd7cdd2ec4be85f4/opentelemetry_instrumentation_celery-0.61b0.tar.gz", hash = "sha256:0e352a567dc89ed8bc083fc635035ce3c5b96bbbd92831ffd676e93b87f8e94f", size = 14780, upload-time = "2026-03-04T14:20:27.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/43/e79108a804d16b1dc8ff28edd0e94ac393cf6359a5adcd7cdd2ec4be85f4/opentelemetry_instrumentation_celery-0.61b0.tar.gz", hash = "sha256:0e352a567dc89ed8bc083fc635035ce3c5b96bbbd92831ffd676e93b87f8e94f", size = 14780 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/ed/c05f3c84b455654eb6c047474ffde61ed92efc24030f64213c98bca9d44b/opentelemetry_instrumentation_celery-0.61b0-py3-none-any.whl", hash = "sha256:01235733ff0cdf571cb03b270645abb14b9c8d830313dc5842097ec90146320b", size = 13856, upload-time = "2026-03-04T14:19:20.98Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ed/c05f3c84b455654eb6c047474ffde61ed92efc24030f64213c98bca9d44b/opentelemetry_instrumentation_celery-0.61b0-py3-none-any.whl", hash = "sha256:01235733ff0cdf571cb03b270645abb14b9c8d830313dc5842097ec90146320b", size = 13856 }, ] [[package]] @@ -5345,9 +5337,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/ed/ba91c9e4a3ec65781e9c59982109f0a36de9fa574f622596b33d1985dab5/opentelemetry_instrumentation_dbapi-0.61b0.tar.gz", hash = "sha256:02fa800682c1de87dcad0e59f2092b3b6fb8b8ea0636518f989e1166b418dcb9", size = 16761, upload-time = "2026-03-04T14:20:29.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/ed/ba91c9e4a3ec65781e9c59982109f0a36de9fa574f622596b33d1985dab5/opentelemetry_instrumentation_dbapi-0.61b0.tar.gz", hash = "sha256:02fa800682c1de87dcad0e59f2092b3b6fb8b8ea0636518f989e1166b418dcb9", size = 16761 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/a5/d26c68f3fd33eb7410985cef7700bb426e2c4a26de9207902cbbffb19a3f/opentelemetry_instrumentation_dbapi-0.61b0-py3-none-any.whl", hash = "sha256:8f762c39c8edd20c6aef3282550a2cfbfec76c3f431bf5c36327dcf9ece2e5a0", size = 14134, upload-time = "2026-03-04T14:19:24.718Z" }, + { url = "https://files.pythonhosted.org/packages/73/a5/d26c68f3fd33eb7410985cef7700bb426e2c4a26de9207902cbbffb19a3f/opentelemetry_instrumentation_dbapi-0.61b0-py3-none-any.whl", hash = "sha256:8f762c39c8edd20c6aef3282550a2cfbfec76c3f431bf5c36327dcf9ece2e5a0", size = 14134 }, ] [[package]] @@ -5361,9 +5353,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/35/aa727bb6e6ef930dcdc96a617b83748fece57b43c47d83ba8d83fbeca657/opentelemetry_instrumentation_fastapi-0.61b0.tar.gz", hash = "sha256:3a24f35b07c557ae1bbc483bf8412221f25d79a405f8b047de8b670722e2fa9f", size = 24800, upload-time = "2026-03-04T14:20:32.759Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/35/aa727bb6e6ef930dcdc96a617b83748fece57b43c47d83ba8d83fbeca657/opentelemetry_instrumentation_fastapi-0.61b0.tar.gz", hash = "sha256:3a24f35b07c557ae1bbc483bf8412221f25d79a405f8b047de8b670722e2fa9f", size = 24800 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/05/acfeb2cccd434242a0a7d0ea29afaf077e04b42b35b485d89aee4e0d9340/opentelemetry_instrumentation_fastapi-0.61b0-py3-none-any.whl", hash = "sha256:a1a844d846540d687d377516b2ff698b51d87c781b59f47c214359c4a241047c", size = 13485, upload-time = "2026-03-04T14:19:30.351Z" }, + { url = "https://files.pythonhosted.org/packages/91/05/acfeb2cccd434242a0a7d0ea29afaf077e04b42b35b485d89aee4e0d9340/opentelemetry_instrumentation_fastapi-0.61b0-py3-none-any.whl", hash = "sha256:a1a844d846540d687d377516b2ff698b51d87c781b59f47c214359c4a241047c", size = 13485 }, ] [[package]] @@ -5377,9 +5369,9 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104, upload-time = "2026-03-04T14:20:34.752Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104 } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198, upload-time = "2026-03-04T14:19:33.585Z" }, + { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198 }, ] [[package]] @@ -5390,9 +5382,9 @@ dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/69473f925acfe2d4edf5c23bcced36906ac3627aa7c5722a8e3f60825f3b/opentelemetry_instrumentation_logging-0.61b0.tar.gz", hash = "sha256:feaa30b700acd2a37cc81db5f562ab0c3a5b6cc2453595e98b72c01dcf649584", size = 17906, upload-time = "2026-03-04T14:20:37.398Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/69473f925acfe2d4edf5c23bcced36906ac3627aa7c5722a8e3f60825f3b/opentelemetry_instrumentation_logging-0.61b0.tar.gz", hash = "sha256:feaa30b700acd2a37cc81db5f562ab0c3a5b6cc2453595e98b72c01dcf649584", size = 17906 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076, upload-time = "2026-03-04T14:19:36.765Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076 }, ] [[package]] @@ -5404,9 +5396,9 @@ dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-instrumentation-dbapi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/00/b98148b3054eb8301a56d523de82ee2fd86a047dba38330c2404d85496e3/opentelemetry_instrumentation_psycopg-0.61b0.tar.gz", hash = "sha256:74e9fed3802945f7ae335cffc30fd18cf58c34a4d0619315f799fa21eb5c74ff", size = 11907, upload-time = "2026-03-04T14:20:39.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/00/b98148b3054eb8301a56d523de82ee2fd86a047dba38330c2404d85496e3/opentelemetry_instrumentation_psycopg-0.61b0.tar.gz", hash = "sha256:74e9fed3802945f7ae335cffc30fd18cf58c34a4d0619315f799fa21eb5c74ff", size = 11907 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/2b/3c36bfc6dc82a7c50c769aff407eaf32e688d655bc61a774609d96b55603/opentelemetry_instrumentation_psycopg-0.61b0-py3-none-any.whl", hash = "sha256:a3e242cad56c0ad4f4f872017c73ce7e6c7012081dda6bd0d776c127fedc358a", size = 11662, upload-time = "2026-03-04T14:19:41.108Z" }, + { url = "https://files.pythonhosted.org/packages/db/2b/3c36bfc6dc82a7c50c769aff407eaf32e688d655bc61a774609d96b55603/opentelemetry_instrumentation_psycopg-0.61b0-py3-none-any.whl", hash = "sha256:a3e242cad56c0ad4f4f872017c73ce7e6c7012081dda6bd0d776c127fedc358a", size = 11662 }, ] [[package]] @@ -5419,9 +5411,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/21/26205f89358a5f2be3ee5512d3d3bce16b622977f64aeaa9d3fa8887dd39/opentelemetry_instrumentation_redis-0.61b0.tar.gz", hash = "sha256:ae0fbb56be9a641e621d55b02a7d62977a2c77c5ee760addd79b9b266e46e523", size = 14781, upload-time = "2026-03-04T14:20:45.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/21/26205f89358a5f2be3ee5512d3d3bce16b622977f64aeaa9d3fa8887dd39/opentelemetry_instrumentation_redis-0.61b0.tar.gz", hash = "sha256:ae0fbb56be9a641e621d55b02a7d62977a2c77c5ee760addd79b9b266e46e523", size = 14781 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/e1/8f4c8e4194291dbe828aeabe779050a8497b379ad90040a5a0a7074b1d08/opentelemetry_instrumentation_redis-0.61b0-py3-none-any.whl", hash = "sha256:8d4e850bbb5f8eeafa44c0eac3a007990c7125de187bc9c3659e29ff7e091172", size = 15506, upload-time = "2026-03-04T14:19:48.588Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e1/8f4c8e4194291dbe828aeabe779050a8497b379ad90040a5a0a7074b1d08/opentelemetry_instrumentation_redis-0.61b0-py3-none-any.whl", hash = "sha256:8d4e850bbb5f8eeafa44c0eac3a007990c7125de187bc9c3659e29ff7e091172", size = 15506 }, ] [[package]] @@ -5435,9 +5427,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/4f/3a325b180944610697a0a926d49d782b41a86120050d44fefb2715b630ac/opentelemetry_instrumentation_sqlalchemy-0.61b0.tar.gz", hash = "sha256:13a3a159a2043a52f0180b3757fbaa26741b0e08abb50deddce4394c118956e6", size = 15343, upload-time = "2026-03-04T14:20:47.648Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/4f/3a325b180944610697a0a926d49d782b41a86120050d44fefb2715b630ac/opentelemetry_instrumentation_sqlalchemy-0.61b0.tar.gz", hash = "sha256:13a3a159a2043a52f0180b3757fbaa26741b0e08abb50deddce4394c118956e6", size = 15343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/97/b906a930c6a1a20c53ecc8b58cabc2cdd0ce560a2b5d44259084ffe4333e/opentelemetry_instrumentation_sqlalchemy-0.61b0-py3-none-any.whl", hash = "sha256:f115e0be54116ba4c327b8d7b68db4045ee18d44439d888ab8130a549c50d1c1", size = 14547, upload-time = "2026-03-04T14:19:53.088Z" }, + { url = "https://files.pythonhosted.org/packages/1f/97/b906a930c6a1a20c53ecc8b58cabc2cdd0ce560a2b5d44259084ffe4333e/opentelemetry_instrumentation_sqlalchemy-0.61b0-py3-none-any.whl", hash = "sha256:f115e0be54116ba4c327b8d7b68db4045ee18d44439d888ab8130a549c50d1c1", size = 14547 }, ] [[package]] @@ -5447,9 +5439,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073 }, ] [[package]] @@ -5461,9 +5453,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951 }, ] [[package]] @@ -5474,128 +5466,128 @@ dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621 }, ] [[package]] name = "opentelemetry-util-http" version = "0.61b0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361, upload-time = "2026-03-04T14:20:57.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281, upload-time = "2026-03-04T14:20:08.364Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281 }, ] [[package]] name = "orjson" version = "3.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992 } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, - { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, - { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, - { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, - { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, - { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, - { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, - { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, - { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, - { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, - { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, - { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, - { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, - { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, - { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, - { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, - { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, - { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, - { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, - { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, - { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, - { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, - { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, - { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545 }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224 }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154 }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548 }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000 }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686 }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812 }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386 }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853 }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130 }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818 }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923 }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007 }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089 }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390 }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189 }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106 }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363 }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007 }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667 }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832 }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373 }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307 }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695 }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099 }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806 }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914 }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986 }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045 }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391 }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188 }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097 }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364 }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076 }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705 }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855 }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386 }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295 }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720 }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152 }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814 }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997 }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985 }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038 }, ] [[package]] name = "ormsgpack" version = "1.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, - { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, - { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, - { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, - { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, - { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, - { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, - { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, - { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, - { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, - { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, - { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, - { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, - { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, - { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, - { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, - { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, - { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, - { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618 }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186 }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738 }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569 }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166 }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498 }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518 }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462 }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559 }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661 }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194 }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778 }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592 }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164 }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516 }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539 }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459 }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577 }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717 }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183 }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814 }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634 }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139 }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578 }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539 }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493 }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579 }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721 }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170 }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816 }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232 }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] [[package]] @@ -5607,56 +5599,56 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, - { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, - { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, - { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, - { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, - { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, - { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, - { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, - { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, - { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, - { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, - { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, - { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, - { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, - { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, - { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, - { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, - { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" }, - { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" }, - { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" }, - { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" }, - { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" }, - { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" }, - { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" }, - { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" }, - { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" }, - { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" }, - { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" }, - { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357 }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543 }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030 }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435 }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133 }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065 }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627 }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483 }, + { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509 }, + { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561 }, + { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506 }, + { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196 }, + { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859 }, + { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584 }, + { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769 }, + { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855 }, + { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673 }, + { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967 }, + { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575 }, + { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921 }, + { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191 }, + { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256 }, + { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047 }, + { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465 }, + { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632 }, + { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535 }, + { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940 }, + { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711 }, + { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918 }, + { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099 }, + { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333 }, + { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664 }, + { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286 }, + { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050 }, + { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055 }, + { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632 }, + { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974 }, + { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622 }, + { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085 }, ] [[package]] name = "pathspec" version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206 }, ] [[package]] @@ -5666,9 +5658,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/d8/b280f01045555dc257b8153c00dee3bc75830f91a744cd5f84ef3a0a64b1/pdf2image-1.17.0.tar.gz", hash = "sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57", size = 12811, upload-time = "2024-01-07T20:33:01.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/d8/b280f01045555dc257b8153c00dee3bc75830f91a744cd5f84ef3a0a64b1/pdf2image-1.17.0.tar.gz", hash = "sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57", size = 12811 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/33/61766ae033518957f877ab246f87ca30a85b778ebaad65b7f74fa7e52988/pdf2image-1.17.0-py3-none-any.whl", hash = "sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2", size = 11618, upload-time = "2024-01-07T20:32:59.957Z" }, + { url = "https://files.pythonhosted.org/packages/62/33/61766ae033518957f877ab246f87ca30a85b778ebaad65b7f74fa7e52988/pdf2image-1.17.0-py3-none-any.whl", hash = "sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2", size = 11618 }, ] [[package]] @@ -5679,9 +5671,9 @@ dependencies = [ { name = "charset-normalizer" }, { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" }, + { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252 }, ] [[package]] @@ -5691,9 +5683,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/d8/fd6009cee3e03214667df488cdcf9609461d729968da94e4f95d6359d304/pgvector-0.3.6.tar.gz", hash = "sha256:31d01690e6ea26cea8a633cde5f0f55f5b246d9c8292d68efdef8c22ec994ade", size = 25421, upload-time = "2024-10-27T00:15:09.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/d8/fd6009cee3e03214667df488cdcf9609461d729968da94e4f95d6359d304/pgvector-0.3.6.tar.gz", hash = "sha256:31d01690e6ea26cea8a633cde5f0f55f5b246d9c8292d68efdef8c22ec994ade", size = 25421 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/81/f457d6d361e04d061bef413749a6e1ab04d98cfeec6d8abcfe40184750f3/pgvector-0.3.6-py3-none-any.whl", hash = "sha256:f6c269b3c110ccb7496bac87202148ed18f34b390a0189c783e351062400a75a", size = 24880, upload-time = "2024-10-27T00:15:08.045Z" }, + { url = "https://files.pythonhosted.org/packages/fb/81/f457d6d361e04d061bef413749a6e1ab04d98cfeec6d8abcfe40184750f3/pgvector-0.3.6-py3-none-any.whl", hash = "sha256:f6c269b3c110ccb7496bac87202148ed18f34b390a0189c783e351062400a75a", size = 24880 }, ] [[package]] @@ -5707,9 +5699,9 @@ dependencies = [ { name = "segments" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/fa/9294d2f11890ca49d0bdac7a4da60cbe5686629bfd4987cae0ad75e051cc/phonemizer_fork-3.3.2.tar.gz", hash = "sha256:10e16e827d0443b087062e21b55e805c00989cf1343b2e81e734cae5f6c0cf69", size = 300989, upload-time = "2025-01-30T13:02:31.201Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/fa/9294d2f11890ca49d0bdac7a4da60cbe5686629bfd4987cae0ad75e051cc/phonemizer_fork-3.3.2.tar.gz", hash = "sha256:10e16e827d0443b087062e21b55e805c00989cf1343b2e81e734cae5f6c0cf69", size = 300989 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/f1/0dcce21b0ae16a82df4b6583f8f3ad8e55b35f7e98b6bf536a4dd225fa08/phonemizer_fork-3.3.2-py3-none-any.whl", hash = "sha256:97305c76f4183b3825dae8f4c032265fe78c9946ce58c47d4b62161349264b74", size = 82700, upload-time = "2025-01-30T13:02:28.667Z" }, + { url = "https://files.pythonhosted.org/packages/64/f1/0dcce21b0ae16a82df4b6583f8f3ad8e55b35f7e98b6bf536a4dd225fa08/phonemizer_fork-3.3.2-py3-none-any.whl", hash = "sha256:97305c76f4183b3825dae8f4c032265fe78c9946ce58c47d4b62161349264b74", size = 82700 }, ] [[package]] @@ -5719,36 +5711,36 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/4a/4a18057a7b64254abdcc4f78d92503fc4f5b8fcc66da118ba87989111ee8/pi_heif-1.3.0.tar.gz", hash = "sha256:58151840d0d60507330654a466b06cbf7ca8fb3759eadb5234d70b4dc2bc990c", size = 17131114, upload-time = "2026-02-27T12:22:40.544Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/4a/4a18057a7b64254abdcc4f78d92503fc4f5b8fcc66da118ba87989111ee8/pi_heif-1.3.0.tar.gz", hash = "sha256:58151840d0d60507330654a466b06cbf7ca8fb3759eadb5234d70b4dc2bc990c", size = 17131114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/eb/4cb3f9789c2fff42ca0b40b0f57fc2a72f68cf62d54c836864cbc2032ec6/pi_heif-1.3.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:09cba007708cef90f95c15c382ece6f51e7ba33fb7fce96b54d786b02c9544e6", size = 1047196, upload-time = "2026-02-27T12:21:58.035Z" }, - { url = "https://files.pythonhosted.org/packages/d2/58/5aeeec1b7f0030902f9d96b168f26b7adaae0c8f758262bba0fa489036a4/pi_heif-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04ce68ac95103d59b5c8fd25a8a51b40541e76d161d0eff834b9a9a3350fa401", size = 942299, upload-time = "2026-02-27T12:21:59.041Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5b/d706a05b96945aabb122932028f14c21524a81e9655f38fad40de9c096f1/pi_heif-1.3.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7aa8e52e3d736cc07dd0657f87c841be069954a7717ecd6fd24ca8afcc16f6cb", size = 1361016, upload-time = "2026-02-27T12:22:00.039Z" }, - { url = "https://files.pythonhosted.org/packages/90/78/c7e141f8a9943d711a63d1f9c55b4f69b6cad0718d8c80e3a65ca3d42a61/pi_heif-1.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed464485f7df1d1b575dc1ff539182b09b8312d06c141882bbcfd428dc842cb1", size = 1489604, upload-time = "2026-02-27T12:22:01.096Z" }, - { url = "https://files.pythonhosted.org/packages/a5/26/06f0ba0fcb6a800d8afa73e63c78be6baaae0c442d17da13ff3e7d9033af/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c2f7d26435d25be915914aba7ed383025a594453e3e84fd297975a9584b580c", size = 2343656, upload-time = "2026-02-27T12:22:02.153Z" }, - { url = "https://files.pythonhosted.org/packages/87/f5/9deb76f59f36451dea69ebf0330171c1f953ae514dd03ac82ef2aa902ee3/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:26b3d101f838fbacebaa63e0c8b60a4333ba4d3fe93f4a3b51169ecaaf13c0ac", size = 2507970, upload-time = "2026-02-27T12:22:03.23Z" }, - { url = "https://files.pythonhosted.org/packages/95/08/41c95822b8bbbd61a15e34a25e9a170035a17ef64bf12f95ad0040441b2e/pi_heif-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:633b6053875b8e482538fdc18cf66ba1f94ce7704d244aa325ed7197073155ee", size = 1946959, upload-time = "2026-02-27T12:22:04.672Z" }, - { url = "https://files.pythonhosted.org/packages/87/a3/e921a28ea4b24bbd96cb9e1cd9272ab9a6525e875dcf1fadaeaf73369e81/pi_heif-1.3.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:1b151e3fb9a0ac4f3729da083eacca2ec4389d312d879ac4e01bb6a1c5fa0812", size = 1047186, upload-time = "2026-02-27T12:22:05.778Z" }, - { url = "https://files.pythonhosted.org/packages/68/c9/ea00b10871c63bc856760a47f9a40b2d6c3c50aaff2e7bc336b6f1205749/pi_heif-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee96ef255f37df9ed0b2d7865e6a746ff594d328c510ee457913f2f677c4f759", size = 942286, upload-time = "2026-02-27T12:22:06.799Z" }, - { url = "https://files.pythonhosted.org/packages/36/28/3accdd524cc56417df99a87d0e1416656100fe3e13e6aee42f5657540eb5/pi_heif-1.3.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d73d35540119e3ccce88a070fbe10e1cf29d119b149bd344c40ac30824edc8f5", size = 1361062, upload-time = "2026-02-27T12:22:08.56Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/e68468fea402318a1a422467b1077a053ac192281bdd04625a452c3e13ad/pi_heif-1.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd610ad8bc319e78c65e106da2ab71f3f4ba85851f77c1634e7c2352a09e7f97", size = 1489616, upload-time = "2026-02-27T12:22:09.815Z" }, - { url = "https://files.pythonhosted.org/packages/46/9b/470790bb3f37ac52edaba9f4b6ec315060fb0e9114e6ac9b8a704754f1d3/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:baedb73888a9d7c2dc2cfe86831c725b6ee640d6405b709d801e09409a7d0da6", size = 2343656, upload-time = "2026-02-27T12:22:11.199Z" }, - { url = "https://files.pythonhosted.org/packages/15/50/17dcf1f8c05eb1cc0ebd479faba3f5832eb5f2dc477ce48d772bebca196c/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:74488dc873986f584beb27c25fa1484a9d9ae10272f442a2571ca771915c28ea", size = 2508037, upload-time = "2026-02-27T12:22:12.212Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6f/5c246d55bcdcfbfdc3d43dbc29c8a845c6b1c7739c4c88b0b29b93956003/pi_heif-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ce66f8ce661f5fb15e73ed91f697cec116ce41a6c6849e8b70ead1d3ad60973", size = 1946953, upload-time = "2026-02-27T12:22:13.532Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e6/a4c05ae1fe025f5fe3839b8ab277a6dc861c5feac5214e286bc277ae5ae3/pi_heif-1.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c00a918a20fb8da1883b3142506c0acb52ecff7901014962aa8d30b3ab78a5e2", size = 1047211, upload-time = "2026-02-27T12:22:14.835Z" }, - { url = "https://files.pythonhosted.org/packages/86/fe/b99741aa4ebd31a28ed4f1bb5703b242211b2968aec15f574a7c75993c89/pi_heif-1.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e224db6932794bde6d18a2f4e417785a3944b8a61a6b582d8473026b5cdf0408", size = 942366, upload-time = "2026-02-27T12:22:15.942Z" }, - { url = "https://files.pythonhosted.org/packages/f9/2b/2a07a116a843a70b4f1320d75727ec2ab616609a4f84201fcbeb72afc685/pi_heif-1.3.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab4764fbf8ec958c6c2b3643a2fa313a7f0275649783ce99ed68a1ce5b71ea96", size = 1361322, upload-time = "2026-02-27T12:22:16.939Z" }, - { url = "https://files.pythonhosted.org/packages/56/3c/93fb4aa1734722d4182ad521832c8e5009934d453b157e994b36e4444c02/pi_heif-1.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f84471adc59a80b06476aba241cfd7c56550ba891a3b6525f5b7aa8eadf8166b", size = 1489732, upload-time = "2026-02-27T12:22:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5c/62f7be4abb279c8ff69bad8c811cdb1224618ab0c5c857ffdb9b4149dc28/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dc2cd95a871d26d604d2a6bbf99c4e7644afbe0d302cdf34065deca41f8a2c30", size = 2343780, upload-time = "2026-02-27T12:22:18.996Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7c/26bdeb9f632058d8558e409c37dddd069e58c726286247d693ecef833516/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71f568ec93271bedd53917e59f617cf2410dbd8ca307e4bd55e319110d253bc1", size = 2508113, upload-time = "2026-02-27T12:22:20.066Z" }, - { url = "https://files.pythonhosted.org/packages/60/6b/42a1f0c4544d77d87116bb9ca77040566254ec45de5bca5e7201e0b56a6e/pi_heif-1.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:caefadb3a8fcfb7857cd065038b24263b286ddd2ecfd8c8a6c01618d00cc8543", size = 2015496, upload-time = "2026-02-27T12:22:21.102Z" }, - { url = "https://files.pythonhosted.org/packages/95/2a/03baff344d2d664ca955c8d8797920bae49d66c8928134c0a071ab6e0319/pi_heif-1.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3ffaf9a8a73c686cf6c24aedc9151f06c776591db47ff4245ee8a41a23f1cd22", size = 1048171, upload-time = "2026-02-27T12:22:22.137Z" }, - { url = "https://files.pythonhosted.org/packages/33/06/6b7f6f7e7d5bb08c720d04b15c67d4802154d4516feb371e46dd3d0f6698/pi_heif-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:42db92eb41825e9a3cb58a497bd382e61478dd4e2b0e531cdec3f5ddc2f6cefc", size = 943106, upload-time = "2026-02-27T12:22:23.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/21/75c676f96307eef0da33955481658adbedfff85c37f943b9ed528f633a76/pi_heif-1.3.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ea595ea1fdd64dbcc29e4ab4e84902b22ef16812a12f459e876b3928d35c848", size = 1366398, upload-time = "2026-02-27T12:22:24.489Z" }, - { url = "https://files.pythonhosted.org/packages/77/aa/b8fb005c0e09dfee67fc4965d12bee41a2333e004574e47e1290a16bf851/pi_heif-1.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86d10a002567de7e7b2da6ae993fb5c99d6f6a727c9b457e238987b047ad7f98", size = 1493859, upload-time = "2026-02-27T12:22:25.634Z" }, - { url = "https://files.pythonhosted.org/packages/d2/55/f76fba8d8ca1b95d89673e72067455ea1ba85c8d4cacacb0cee4c4882f52/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:95651a2a628ea1560e9f2669f9bb58ecbd02436cc52b6a8f2fff91d4f73107fb", size = 2348962, upload-time = "2026-02-27T12:22:26.992Z" }, - { url = "https://files.pythonhosted.org/packages/57/5a/af51148cf5804a120615548e5ec2fee2f22c19b1d88a0ee705a9f09b9f75/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:437f424d8d8bad9f4f23ee4febd8e93b4a2800746e45f676f4543435a7938ca1", size = 2512181, upload-time = "2026-02-27T12:22:29.11Z" }, - { url = "https://files.pythonhosted.org/packages/be/be/83f6f42c1a82cd3eb4a4d85abad9dbf917d4340ece240ba403ee4150de88/pi_heif-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:eba226ab71b1f6fde28a020bc3aeb4c6f2daad1cb7784f7dd57f85f9ef204892", size = 2016126, upload-time = "2026-02-27T12:22:30.377Z" }, + { url = "https://files.pythonhosted.org/packages/1e/eb/4cb3f9789c2fff42ca0b40b0f57fc2a72f68cf62d54c836864cbc2032ec6/pi_heif-1.3.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:09cba007708cef90f95c15c382ece6f51e7ba33fb7fce96b54d786b02c9544e6", size = 1047196 }, + { url = "https://files.pythonhosted.org/packages/d2/58/5aeeec1b7f0030902f9d96b168f26b7adaae0c8f758262bba0fa489036a4/pi_heif-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04ce68ac95103d59b5c8fd25a8a51b40541e76d161d0eff834b9a9a3350fa401", size = 942299 }, + { url = "https://files.pythonhosted.org/packages/b2/5b/d706a05b96945aabb122932028f14c21524a81e9655f38fad40de9c096f1/pi_heif-1.3.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7aa8e52e3d736cc07dd0657f87c841be069954a7717ecd6fd24ca8afcc16f6cb", size = 1361016 }, + { url = "https://files.pythonhosted.org/packages/90/78/c7e141f8a9943d711a63d1f9c55b4f69b6cad0718d8c80e3a65ca3d42a61/pi_heif-1.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed464485f7df1d1b575dc1ff539182b09b8312d06c141882bbcfd428dc842cb1", size = 1489604 }, + { url = "https://files.pythonhosted.org/packages/a5/26/06f0ba0fcb6a800d8afa73e63c78be6baaae0c442d17da13ff3e7d9033af/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c2f7d26435d25be915914aba7ed383025a594453e3e84fd297975a9584b580c", size = 2343656 }, + { url = "https://files.pythonhosted.org/packages/87/f5/9deb76f59f36451dea69ebf0330171c1f953ae514dd03ac82ef2aa902ee3/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:26b3d101f838fbacebaa63e0c8b60a4333ba4d3fe93f4a3b51169ecaaf13c0ac", size = 2507970 }, + { url = "https://files.pythonhosted.org/packages/95/08/41c95822b8bbbd61a15e34a25e9a170035a17ef64bf12f95ad0040441b2e/pi_heif-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:633b6053875b8e482538fdc18cf66ba1f94ce7704d244aa325ed7197073155ee", size = 1946959 }, + { url = "https://files.pythonhosted.org/packages/87/a3/e921a28ea4b24bbd96cb9e1cd9272ab9a6525e875dcf1fadaeaf73369e81/pi_heif-1.3.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:1b151e3fb9a0ac4f3729da083eacca2ec4389d312d879ac4e01bb6a1c5fa0812", size = 1047186 }, + { url = "https://files.pythonhosted.org/packages/68/c9/ea00b10871c63bc856760a47f9a40b2d6c3c50aaff2e7bc336b6f1205749/pi_heif-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee96ef255f37df9ed0b2d7865e6a746ff594d328c510ee457913f2f677c4f759", size = 942286 }, + { url = "https://files.pythonhosted.org/packages/36/28/3accdd524cc56417df99a87d0e1416656100fe3e13e6aee42f5657540eb5/pi_heif-1.3.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d73d35540119e3ccce88a070fbe10e1cf29d119b149bd344c40ac30824edc8f5", size = 1361062 }, + { url = "https://files.pythonhosted.org/packages/f2/11/e68468fea402318a1a422467b1077a053ac192281bdd04625a452c3e13ad/pi_heif-1.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd610ad8bc319e78c65e106da2ab71f3f4ba85851f77c1634e7c2352a09e7f97", size = 1489616 }, + { url = "https://files.pythonhosted.org/packages/46/9b/470790bb3f37ac52edaba9f4b6ec315060fb0e9114e6ac9b8a704754f1d3/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:baedb73888a9d7c2dc2cfe86831c725b6ee640d6405b709d801e09409a7d0da6", size = 2343656 }, + { url = "https://files.pythonhosted.org/packages/15/50/17dcf1f8c05eb1cc0ebd479faba3f5832eb5f2dc477ce48d772bebca196c/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:74488dc873986f584beb27c25fa1484a9d9ae10272f442a2571ca771915c28ea", size = 2508037 }, + { url = "https://files.pythonhosted.org/packages/c9/6f/5c246d55bcdcfbfdc3d43dbc29c8a845c6b1c7739c4c88b0b29b93956003/pi_heif-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ce66f8ce661f5fb15e73ed91f697cec116ce41a6c6849e8b70ead1d3ad60973", size = 1946953 }, + { url = "https://files.pythonhosted.org/packages/ea/e6/a4c05ae1fe025f5fe3839b8ab277a6dc861c5feac5214e286bc277ae5ae3/pi_heif-1.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c00a918a20fb8da1883b3142506c0acb52ecff7901014962aa8d30b3ab78a5e2", size = 1047211 }, + { url = "https://files.pythonhosted.org/packages/86/fe/b99741aa4ebd31a28ed4f1bb5703b242211b2968aec15f574a7c75993c89/pi_heif-1.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e224db6932794bde6d18a2f4e417785a3944b8a61a6b582d8473026b5cdf0408", size = 942366 }, + { url = "https://files.pythonhosted.org/packages/f9/2b/2a07a116a843a70b4f1320d75727ec2ab616609a4f84201fcbeb72afc685/pi_heif-1.3.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab4764fbf8ec958c6c2b3643a2fa313a7f0275649783ce99ed68a1ce5b71ea96", size = 1361322 }, + { url = "https://files.pythonhosted.org/packages/56/3c/93fb4aa1734722d4182ad521832c8e5009934d453b157e994b36e4444c02/pi_heif-1.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f84471adc59a80b06476aba241cfd7c56550ba891a3b6525f5b7aa8eadf8166b", size = 1489732 }, + { url = "https://files.pythonhosted.org/packages/2a/5c/62f7be4abb279c8ff69bad8c811cdb1224618ab0c5c857ffdb9b4149dc28/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dc2cd95a871d26d604d2a6bbf99c4e7644afbe0d302cdf34065deca41f8a2c30", size = 2343780 }, + { url = "https://files.pythonhosted.org/packages/e5/7c/26bdeb9f632058d8558e409c37dddd069e58c726286247d693ecef833516/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71f568ec93271bedd53917e59f617cf2410dbd8ca307e4bd55e319110d253bc1", size = 2508113 }, + { url = "https://files.pythonhosted.org/packages/60/6b/42a1f0c4544d77d87116bb9ca77040566254ec45de5bca5e7201e0b56a6e/pi_heif-1.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:caefadb3a8fcfb7857cd065038b24263b286ddd2ecfd8c8a6c01618d00cc8543", size = 2015496 }, + { url = "https://files.pythonhosted.org/packages/95/2a/03baff344d2d664ca955c8d8797920bae49d66c8928134c0a071ab6e0319/pi_heif-1.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3ffaf9a8a73c686cf6c24aedc9151f06c776591db47ff4245ee8a41a23f1cd22", size = 1048171 }, + { url = "https://files.pythonhosted.org/packages/33/06/6b7f6f7e7d5bb08c720d04b15c67d4802154d4516feb371e46dd3d0f6698/pi_heif-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:42db92eb41825e9a3cb58a497bd382e61478dd4e2b0e531cdec3f5ddc2f6cefc", size = 943106 }, + { url = "https://files.pythonhosted.org/packages/5c/21/75c676f96307eef0da33955481658adbedfff85c37f943b9ed528f633a76/pi_heif-1.3.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ea595ea1fdd64dbcc29e4ab4e84902b22ef16812a12f459e876b3928d35c848", size = 1366398 }, + { url = "https://files.pythonhosted.org/packages/77/aa/b8fb005c0e09dfee67fc4965d12bee41a2333e004574e47e1290a16bf851/pi_heif-1.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86d10a002567de7e7b2da6ae993fb5c99d6f6a727c9b457e238987b047ad7f98", size = 1493859 }, + { url = "https://files.pythonhosted.org/packages/d2/55/f76fba8d8ca1b95d89673e72067455ea1ba85c8d4cacacb0cee4c4882f52/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:95651a2a628ea1560e9f2669f9bb58ecbd02436cc52b6a8f2fff91d4f73107fb", size = 2348962 }, + { url = "https://files.pythonhosted.org/packages/57/5a/af51148cf5804a120615548e5ec2fee2f22c19b1d88a0ee705a9f09b9f75/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:437f424d8d8bad9f4f23ee4febd8e93b4a2800746e45f676f4543435a7938ca1", size = 2512181 }, + { url = "https://files.pythonhosted.org/packages/be/be/83f6f42c1a82cd3eb4a4d85abad9dbf917d4340ece240ba403ee4150de88/pi_heif-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:eba226ab71b1f6fde28a020bc3aeb4c6f2daad1cb7784f7dd57f85f9ef204892", size = 2016126 }, ] [[package]] @@ -5761,98 +5753,98 @@ dependencies = [ { name = "packaging" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/66/32a45480d84cb239c7ad31209c956496fe5b20f6fb163d794db4c79f840c/pikepdf-10.5.1.tar.gz", hash = "sha256:ffa6c7d0b77deb3af9735e0b0cae177c897431e10d342bb171b62e5527a622b7", size = 4582470, upload-time = "2026-03-18T07:56:00.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/66/32a45480d84cb239c7ad31209c956496fe5b20f6fb163d794db4c79f840c/pikepdf-10.5.1.tar.gz", hash = "sha256:ffa6c7d0b77deb3af9735e0b0cae177c897431e10d342bb171b62e5527a622b7", size = 4582470 } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/c8/f0c8ea17555e6bfffa5f598988edc9f1c5861f9909ca72ee745362958453/pikepdf-10.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:03665c0d3658f4bb6084dd65d2db3a44f5af2ef0cd005cbb2ef0af82bcad8c83", size = 4772405, upload-time = "2026-03-18T07:55:20.562Z" }, - { url = "https://files.pythonhosted.org/packages/b8/90/9c201894f8a27a2dad1b6dce92dd497e785e81f4f902f2e261ee04e8c1d6/pikepdf-10.5.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:141dab118d6462abf9324f3fe79f18f597db75c6ac96e90984b65f5544e540a3", size = 5089114, upload-time = "2026-03-18T07:55:22.298Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e1/2a0f82254265d432ee0b7323cf897fcbc062f8036853a0353ced58cb5521/pikepdf-10.5.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5d5d0fbfd54acfce3496693f1378d0a0c43025ad96abeb2ffe466737bddaaa0", size = 2491105, upload-time = "2026-03-18T07:55:23.899Z" }, - { url = "https://files.pythonhosted.org/packages/92/23/2d56b5a478aa62d5b1307aa273ca3bb67ac7db7f948708e3ab9dba9eb6b4/pikepdf-10.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3246732f3733dee4048c69a2141c2c0a80af7c9e1d31f35222d6d0d108e3678", size = 2735333, upload-time = "2026-03-18T07:55:25.527Z" }, - { url = "https://files.pythonhosted.org/packages/a6/dd/9678100282f538e5804eb80d885cf0131b1a7a36ca6acbb204858c52c6bd/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1580124500a328444c68b8b82ba9bf6166c31e02c5e4924e4bbcea2a8d2e7ee0", size = 3700125, upload-time = "2026-03-18T07:55:27.48Z" }, - { url = "https://files.pythonhosted.org/packages/88/2b/70e9ee1257b9f0010083bd3d9a51e648749284892ad3bb9e3a8691799953/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc2b338a157c8aabafd8ecc7f2aab15e45bf2dcd0ebfe388ffff4fb4147a9e97", size = 3908975, upload-time = "2026-03-18T07:55:29.232Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/87cc2fbdcd8ce0a8aeace28c52b0f2acc56cc19a064ec514ed80f246f891/pikepdf-10.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:b220200d96bcaec722c8c8e4a96037515c9d212775587b588fafe692c630a89e", size = 3812237, upload-time = "2026-03-18T07:55:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d4/eb00bb96b383a1dd3151d347a6339408af642d75ed998f8ac7368ddf5bcd/pikepdf-10.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b30d192baf0132e6d945e8b2200288bd32f2b0ec2357b1fe414ef595531b181", size = 4772545, upload-time = "2026-03-18T07:55:33.251Z" }, - { url = "https://files.pythonhosted.org/packages/42/6f/f25b9e66afd647cd090d0e62a5287135ec0ae4971b2f1601a1e3dad96fa9/pikepdf-10.5.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:d59a710ba6fc5a5220ac59dba4bd43612663a2fde33973a616843bc79eaf0fac", size = 5088950, upload-time = "2026-03-18T07:55:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/69/9e/f2781afe47f149f88b1c2a3e72a0f2501fcc104c23bffb2e68c89ec81ea7/pikepdf-10.5.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f245df7aeb1a69c166e923ceae9bf47c895a06286dcb94a92225f1b10156e6f", size = 2490804, upload-time = "2026-03-18T07:55:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/9a/77/f87710f01d74dfe8d3713cfe682b350c77aa7a5443552fffceb7b3b40543/pikepdf-10.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e1cdfdeec93a6eca49e6ce592269fd78007d13440719d6f95f3a5a33e609d9f", size = 2734878, upload-time = "2026-03-18T07:55:39.061Z" }, - { url = "https://files.pythonhosted.org/packages/7b/b1/b350dc5cf82de45c0c1c79fd01384b0af07e3ba82da77e276bc98ca00489/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b21b093335069d79eecf8639b150e6100043b1275ffdeb00501640d2bcbdf760", size = 3699375, upload-time = "2026-03-18T07:55:40.984Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/f7c7473c36687d453bede6afb0a4d8fb0ebb2e846f35219db12542889df1/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:89cc87b440f663f1e4f51670930f0aa310cec30cc02d9a1c36a61432be9380fd", size = 3908458, upload-time = "2026-03-18T07:55:43.051Z" }, - { url = "https://files.pythonhosted.org/packages/38/4a/b2949669f3eaae08cc32d21b13f505ebbcabb0d7dd8808fdf743a9eb69ae/pikepdf-10.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:d10f915c80881be4802204a54ba3ce5ee9e13dd59aa6fbe4cb95230039defa86", size = 3812315, upload-time = "2026-03-18T07:55:44.829Z" }, - { url = "https://files.pythonhosted.org/packages/3c/48/b513468b4a5c7d4d9007c2c9b59686d15cca88c339225e4f2069c15799f9/pikepdf-10.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:ac7a96d6e4a23cd2dfc07f2cec9f55b000fcd4be4be888dbbe5a767dcd4c409e", size = 4770764, upload-time = "2026-03-18T07:55:46.597Z" }, - { url = "https://files.pythonhosted.org/packages/2d/42/da2abc72d0d04b48158b6bccad6e31dfd2cef63f501cdb6192af100d7545/pikepdf-10.5.1-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:1b81fd3f3de40ec4ad87fe9337d91e5f1301d4c4450ff02f4aa581c76609a3b7", size = 5089799, upload-time = "2026-03-18T07:55:48.45Z" }, - { url = "https://files.pythonhosted.org/packages/1d/47/19731ef57be6007fb5007438618d6803d7abb4adaa095e55a8e7bd5cfa25/pikepdf-10.5.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:781ee394f38ebdf412dc674a1e3333a844dbab673d4d8db04050055062b8fa8d", size = 2493677, upload-time = "2026-03-18T07:55:50.219Z" }, - { url = "https://files.pythonhosted.org/packages/25/87/3e115b7a47c3a5bb3a58a7ba51de20d4964393735fab0085fc94a979113b/pikepdf-10.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17e6a136b870424e66167035406662842783049ee14262f49ed2572caa584475", size = 2736452, upload-time = "2026-03-18T07:55:52.266Z" }, - { url = "https://files.pythonhosted.org/packages/ae/2b/f30006c28b58a12116561fe4fe62c9cd630e97a88de799c27a3073c6bb55/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c9ae38d8dd7acf1b4c9bcd9a38fb75735bc62049c34634b1899e47068b314c5f", size = 3702958, upload-time = "2026-03-18T07:55:54.294Z" }, - { url = "https://files.pythonhosted.org/packages/83/47/d467f13394c3be1c57b0fbc4264a8f0d1f6ae42ae61299c4695a89b2d983/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:013411404eb129b8d0cd565ce09c4f99254b9837a8e66f90d150b7c1f23a7124", size = 3910716, upload-time = "2026-03-18T07:55:56.233Z" }, - { url = "https://files.pythonhosted.org/packages/5c/b5/7753d726905f217b78e677fee82e877a6880bc326db3a13c1934884ed54b/pikepdf-10.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:70d1b5ae2e61ab5a08e2c978d18208766d5aa0747a2181c95d6e3acaa114f278", size = 3923717, upload-time = "2026-03-18T07:55:58.16Z" }, + { url = "https://files.pythonhosted.org/packages/71/c8/f0c8ea17555e6bfffa5f598988edc9f1c5861f9909ca72ee745362958453/pikepdf-10.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:03665c0d3658f4bb6084dd65d2db3a44f5af2ef0cd005cbb2ef0af82bcad8c83", size = 4772405 }, + { url = "https://files.pythonhosted.org/packages/b8/90/9c201894f8a27a2dad1b6dce92dd497e785e81f4f902f2e261ee04e8c1d6/pikepdf-10.5.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:141dab118d6462abf9324f3fe79f18f597db75c6ac96e90984b65f5544e540a3", size = 5089114 }, + { url = "https://files.pythonhosted.org/packages/c9/e1/2a0f82254265d432ee0b7323cf897fcbc062f8036853a0353ced58cb5521/pikepdf-10.5.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5d5d0fbfd54acfce3496693f1378d0a0c43025ad96abeb2ffe466737bddaaa0", size = 2491105 }, + { url = "https://files.pythonhosted.org/packages/92/23/2d56b5a478aa62d5b1307aa273ca3bb67ac7db7f948708e3ab9dba9eb6b4/pikepdf-10.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3246732f3733dee4048c69a2141c2c0a80af7c9e1d31f35222d6d0d108e3678", size = 2735333 }, + { url = "https://files.pythonhosted.org/packages/a6/dd/9678100282f538e5804eb80d885cf0131b1a7a36ca6acbb204858c52c6bd/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1580124500a328444c68b8b82ba9bf6166c31e02c5e4924e4bbcea2a8d2e7ee0", size = 3700125 }, + { url = "https://files.pythonhosted.org/packages/88/2b/70e9ee1257b9f0010083bd3d9a51e648749284892ad3bb9e3a8691799953/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc2b338a157c8aabafd8ecc7f2aab15e45bf2dcd0ebfe388ffff4fb4147a9e97", size = 3908975 }, + { url = "https://files.pythonhosted.org/packages/ad/b0/87cc2fbdcd8ce0a8aeace28c52b0f2acc56cc19a064ec514ed80f246f891/pikepdf-10.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:b220200d96bcaec722c8c8e4a96037515c9d212775587b588fafe692c630a89e", size = 3812237 }, + { url = "https://files.pythonhosted.org/packages/7f/d4/eb00bb96b383a1dd3151d347a6339408af642d75ed998f8ac7368ddf5bcd/pikepdf-10.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b30d192baf0132e6d945e8b2200288bd32f2b0ec2357b1fe414ef595531b181", size = 4772545 }, + { url = "https://files.pythonhosted.org/packages/42/6f/f25b9e66afd647cd090d0e62a5287135ec0ae4971b2f1601a1e3dad96fa9/pikepdf-10.5.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:d59a710ba6fc5a5220ac59dba4bd43612663a2fde33973a616843bc79eaf0fac", size = 5088950 }, + { url = "https://files.pythonhosted.org/packages/69/9e/f2781afe47f149f88b1c2a3e72a0f2501fcc104c23bffb2e68c89ec81ea7/pikepdf-10.5.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f245df7aeb1a69c166e923ceae9bf47c895a06286dcb94a92225f1b10156e6f", size = 2490804 }, + { url = "https://files.pythonhosted.org/packages/9a/77/f87710f01d74dfe8d3713cfe682b350c77aa7a5443552fffceb7b3b40543/pikepdf-10.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e1cdfdeec93a6eca49e6ce592269fd78007d13440719d6f95f3a5a33e609d9f", size = 2734878 }, + { url = "https://files.pythonhosted.org/packages/7b/b1/b350dc5cf82de45c0c1c79fd01384b0af07e3ba82da77e276bc98ca00489/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b21b093335069d79eecf8639b150e6100043b1275ffdeb00501640d2bcbdf760", size = 3699375 }, + { url = "https://files.pythonhosted.org/packages/2c/5e/f7c7473c36687d453bede6afb0a4d8fb0ebb2e846f35219db12542889df1/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:89cc87b440f663f1e4f51670930f0aa310cec30cc02d9a1c36a61432be9380fd", size = 3908458 }, + { url = "https://files.pythonhosted.org/packages/38/4a/b2949669f3eaae08cc32d21b13f505ebbcabb0d7dd8808fdf743a9eb69ae/pikepdf-10.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:d10f915c80881be4802204a54ba3ce5ee9e13dd59aa6fbe4cb95230039defa86", size = 3812315 }, + { url = "https://files.pythonhosted.org/packages/3c/48/b513468b4a5c7d4d9007c2c9b59686d15cca88c339225e4f2069c15799f9/pikepdf-10.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:ac7a96d6e4a23cd2dfc07f2cec9f55b000fcd4be4be888dbbe5a767dcd4c409e", size = 4770764 }, + { url = "https://files.pythonhosted.org/packages/2d/42/da2abc72d0d04b48158b6bccad6e31dfd2cef63f501cdb6192af100d7545/pikepdf-10.5.1-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:1b81fd3f3de40ec4ad87fe9337d91e5f1301d4c4450ff02f4aa581c76609a3b7", size = 5089799 }, + { url = "https://files.pythonhosted.org/packages/1d/47/19731ef57be6007fb5007438618d6803d7abb4adaa095e55a8e7bd5cfa25/pikepdf-10.5.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:781ee394f38ebdf412dc674a1e3333a844dbab673d4d8db04050055062b8fa8d", size = 2493677 }, + { url = "https://files.pythonhosted.org/packages/25/87/3e115b7a47c3a5bb3a58a7ba51de20d4964393735fab0085fc94a979113b/pikepdf-10.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17e6a136b870424e66167035406662842783049ee14262f49ed2572caa584475", size = 2736452 }, + { url = "https://files.pythonhosted.org/packages/ae/2b/f30006c28b58a12116561fe4fe62c9cd630e97a88de799c27a3073c6bb55/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c9ae38d8dd7acf1b4c9bcd9a38fb75735bc62049c34634b1899e47068b314c5f", size = 3702958 }, + { url = "https://files.pythonhosted.org/packages/83/47/d467f13394c3be1c57b0fbc4264a8f0d1f6ae42ae61299c4695a89b2d983/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:013411404eb129b8d0cd565ce09c4f99254b9837a8e66f90d150b7c1f23a7124", size = 3910716 }, + { url = "https://files.pythonhosted.org/packages/5c/b5/7753d726905f217b78e677fee82e877a6880bc326db3a13c1934884ed54b/pikepdf-10.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:70d1b5ae2e61ab5a08e2c978d18208766d5aa0747a2181c95d6e3acaa114f278", size = 3923717 }, ] [[package]] name = "pillow" version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, - { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, - { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, - { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, - { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, - { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, - { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, - { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, - { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, - { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, - { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, - { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, - { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, - { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803 }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601 }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995 }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012 }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638 }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540 }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613 }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745 }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823 }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367 }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811 }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689 }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535 }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364 }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561 }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460 }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698 }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706 }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621 }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069 }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040 }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523 }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552 }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108 }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712 }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880 }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616 }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008 }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226 }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136 }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129 }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807 }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954 }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441 }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383 }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104 }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652 }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823 }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143 }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254 }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499 }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137 }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721 }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798 }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315 }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360 }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438 }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503 }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748 }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314 }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612 }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567 }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951 }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769 }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358 }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558 }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028 }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940 }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736 }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446 }, ] [[package]] @@ -5868,9 +5860,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/e4/8303133de5b3850c85d56caf9cc23cc38c74942bb8a940890b225245d7df/pinecone-8.1.0.tar.gz", hash = "sha256:48a00843fb232ccfd57eba618f0c0294e918b030e1bc7e853fb88d04f80ba569", size = 1041965, upload-time = "2026-02-19T20:08:32.999Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/e4/8303133de5b3850c85d56caf9cc23cc38c74942bb8a940890b225245d7df/pinecone-8.1.0.tar.gz", hash = "sha256:48a00843fb232ccfd57eba618f0c0294e918b030e1bc7e853fb88d04f80ba569", size = 1041965 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/f7/beee7033ef92e5964e570fc29a048627e298745916e65c66105378405d06/pinecone-8.1.0-py3-none-any.whl", hash = "sha256:b0ba9c55c9a072fbe4fc7381bc3e5eb1b14550a8007233a3368ada74b1747534", size = 742745, upload-time = "2026-02-19T20:08:31.319Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f7/beee7033ef92e5964e570fc29a048627e298745916e65c66105378405d06/pinecone-8.1.0-py3-none-any.whl", hash = "sha256:b0ba9c55c9a072fbe4fc7381bc3e5eb1b14550a8007233a3368ada74b1747534", size = 742745 }, ] [[package]] @@ -5881,27 +5873,27 @@ dependencies = [ { name = "packaging" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/16/dcaff42ddfeab75dccd17685a0db46489717c3d23753dc14c55770e12aa8/pinecone_plugin_assistant-3.0.2.tar.gz", hash = "sha256:04163af282ad7895b581ab89f850ed139e4ddcea72010cadfa4c573759d5c896", size = 152066, upload-time = "2026-02-01T09:08:48.04Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/16/dcaff42ddfeab75dccd17685a0db46489717c3d23753dc14c55770e12aa8/pinecone_plugin_assistant-3.0.2.tar.gz", hash = "sha256:04163af282ad7895b581ab89f850ed139e4ddcea72010cadfa4c573759d5c896", size = 152066 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/dd/8bc4f3baf6c03acfb0b300f5aba53d19cc3a319281da518182bf22671b92/pinecone_plugin_assistant-3.0.2-py3-none-any.whl", hash = "sha256:de21ff696219fcad6c7ec86a3d1f70875024314537758ab345b6230462342903", size = 280863, upload-time = "2026-02-01T09:08:49.384Z" }, + { url = "https://files.pythonhosted.org/packages/4a/dd/8bc4f3baf6c03acfb0b300f5aba53d19cc3a319281da518182bf22671b92/pinecone_plugin_assistant-3.0.2-py3-none-any.whl", hash = "sha256:de21ff696219fcad6c7ec86a3d1f70875024314537758ab345b6230462342903", size = 280863 }, ] [[package]] name = "pinecone-plugin-interface" version = "0.0.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/fb/e8a4063264953ead9e2b24d9b390152c60f042c951c47f4592e9996e57ff/pinecone_plugin_interface-0.0.7.tar.gz", hash = "sha256:b8e6675e41847333aa13923cc44daa3f85676d7157324682dc1640588a982846", size = 3370, upload-time = "2024-06-05T01:57:52.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/fb/e8a4063264953ead9e2b24d9b390152c60f042c951c47f4592e9996e57ff/pinecone_plugin_interface-0.0.7.tar.gz", hash = "sha256:b8e6675e41847333aa13923cc44daa3f85676d7157324682dc1640588a982846", size = 3370 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/1d/a21fdfcd6d022cb64cef5c2a29ee6691c6c103c4566b41646b080b7536a5/pinecone_plugin_interface-0.0.7-py3-none-any.whl", hash = "sha256:875857ad9c9fc8bbc074dbe780d187a2afd21f5bfe0f3b08601924a61ef1bba8", size = 6249, upload-time = "2024-06-05T01:57:50.583Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1d/a21fdfcd6d022cb64cef5c2a29ee6691c6c103c4566b41646b080b7536a5/pinecone_plugin_interface-0.0.7-py3-none-any.whl", hash = "sha256:875857ad9c9fc8bbc074dbe780d187a2afd21f5bfe0f3b08601924a61ef1bba8", size = 6249 }, ] [[package]] name = "platformdirs" version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216 }, ] [[package]] @@ -5913,23 +5905,23 @@ dependencies = [ { name = "pyee" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098, upload-time = "2026-01-30T15:09:24.028Z" }, - { url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625, upload-time = "2026-01-30T15:09:27.558Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098, upload-time = "2026-01-30T15:09:30.461Z" }, - { url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268, upload-time = "2026-01-30T15:09:33.787Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214, upload-time = "2026-01-30T15:09:36.751Z" }, - { url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998, upload-time = "2026-01-30T15:09:39.627Z" }, - { url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005, upload-time = "2026-01-30T15:09:42.449Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919, upload-time = "2026-01-30T15:09:45.71Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098 }, + { url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625 }, + { url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098 }, + { url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268 }, + { url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214 }, + { url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998 }, + { url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005 }, + { url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919 }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] @@ -5940,9 +5932,9 @@ dependencies = [ { name = "faker" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/68/7717bd9e63ed254617a7d3dc9260904fb736d6ea203e58ffddcb186c64e4/polyfactory-3.3.0.tar.gz", hash = "sha256:237258b6ff43edf362ffd1f68086bb796466f786adfa002b0ac256dbf2246e9a", size = 348668, upload-time = "2026-02-22T09:46:28.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/68/7717bd9e63ed254617a7d3dc9260904fb736d6ea203e58ffddcb186c64e4/polyfactory-3.3.0.tar.gz", hash = "sha256:237258b6ff43edf362ffd1f68086bb796466f786adfa002b0ac256dbf2246e9a", size = 348668 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/34/b6f19941adcdaf415b5e8a8d577499f5b6a76b59cbae37f9b125a9ffe9f2/polyfactory-3.3.0-py3-none-any.whl", hash = "sha256:686abcaa761930d3df87b91e95b26b8d8cb9fdbbbe0b03d5f918acff5c72606e", size = 62707, upload-time = "2026-02-22T09:46:25.985Z" }, + { url = "https://files.pythonhosted.org/packages/dd/34/b6f19941adcdaf415b5e8a8d577499f5b6a76b59cbae37f9b125a9ffe9f2/polyfactory-3.3.0-py3-none-any.whl", hash = "sha256:686abcaa761930d3df87b91e95b26b8d8cb9fdbbbe0b03d5f918acff5c72606e", size = 62707 }, ] [[package]] @@ -5952,9 +5944,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424 }, ] [[package]] @@ -5965,58 +5957,58 @@ dependencies = [ { name = "cymem" }, { name = "murmurhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/75/fe6b7bbd0dea530a001b0e24c331b21a0be2786e402abf3c57f5dce43d4b/preshed-3.0.13.tar.gz", hash = "sha256:d75f718bbfd97e992f7827e0fa7faf6a91bdd9c922d5baa4b50d62731396cb89", size = 18338, upload-time = "2026-03-23T08:57:31.378Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/75/fe6b7bbd0dea530a001b0e24c331b21a0be2786e402abf3c57f5dce43d4b/preshed-3.0.13.tar.gz", hash = "sha256:d75f718bbfd97e992f7827e0fa7faf6a91bdd9c922d5baa4b50d62731396cb89", size = 18338 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/fb/ccff23c44c04088c248539005fcda78b9014512a34d170c5360f02ad908b/preshed-3.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d14eea14bd01291388928991d7df7d60b9fd19ae970e55006eb4d29b0c1e8eb", size = 138497, upload-time = "2026-03-23T08:56:35.321Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ce/cad5a8145881a771e6c0d002f2e585fc19b962f120860b54d32af5baa342/preshed-3.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f05b08ce92399c0655b5e0eb5a1cc1f9e295703ed3aabdfaf6538dfa8ae23d57", size = 138010, upload-time = "2026-03-23T08:56:36.399Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a2/c5fed4fb3e946699259d11e4036a3cfdd8c89b3e542e3077d46781642425/preshed-3.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62cf7f3113132891d6bba70ff547ad81c6fe50a31930bbbb8499f1d47cd122b7", size = 861498, upload-time = "2026-03-23T08:56:37.67Z" }, - { url = "https://files.pythonhosted.org/packages/51/94/8c9bc48a6ea4903f53a1a0031ce8e35687526949f25821762ef21493c007/preshed-3.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b8de3f58043070a354477995acdd98626ce43e4193c708ebd0f694e467f5155", size = 868988, upload-time = "2026-03-23T08:56:39.324Z" }, - { url = "https://files.pythonhosted.org/packages/b6/df/ecd2f40055ff52527ca117ffbfafb888c1a3079b59fbabe03c5b8f9b7240/preshed-3.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:183b339956a9e1d7a4a00038a3b9587a734db9e8bd915939a49791bd1b372156", size = 1847382, upload-time = "2026-03-23T08:56:40.89Z" }, - { url = "https://files.pythonhosted.org/packages/e6/88/bdb244e40284ded3632a9f88c23bc80230bd7b2ae4a8b7f2cc91adead7a8/preshed-3.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e77bed56aded7cbe5d28d6bd2178bc5b13eda0e0e464dab205fb578fa915000", size = 1919236, upload-time = "2026-03-23T08:56:42.616Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c9/c91ea56342e6c364fc69b444a1ac5432327857199c44032c9cc9dc4c3a23/preshed-3.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:04d8f13f2986e5d11af5ac51f55ce3106c70c41b483d20ea392e6180bdd0f870", size = 122938, upload-time = "2026-03-23T08:56:44.271Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0b/6a99d99619fd83b14c696e2489caed7070647488d4d3ac0b723d35db2de0/preshed-3.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:19318dc1cd8cac6663c6c830bf7e0002d2de853769fb03e056774e97c21bedfd", size = 109194, upload-time = "2026-03-23T08:56:45.346Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2a/401158195d6dc7f6aef0b354d74d0e95c9da124499448c2b3dbb95b71204/preshed-3.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0d0c14187dc0078d8a63bf190ec045a4d13e7748b6caeb557a7d575e411410b", size = 137289, upload-time = "2026-03-23T08:56:46.516Z" }, - { url = "https://files.pythonhosted.org/packages/88/8f/e20e64573988528785447a6893b2e7ab287ecfd85b3888e978b28812fd20/preshed-3.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7770987c2e57497cd26124a9be5f652b5b3ccd0def89859ab0da8bca6144a3de", size = 136847, upload-time = "2026-03-23T08:56:47.572Z" }, - { url = "https://files.pythonhosted.org/packages/b9/72/18168f881359c4482d312f8dc196371bdd61c1583a52b34390da4c88bbea/preshed-3.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a7bc48220de579be6bdb0a8715482cf36e2a625a6fd5ad26c9f43485a4a23b5", size = 831478, upload-time = "2026-03-23T08:56:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3a/3543476091087102775568cea9885dde3453569e9aeee365809108de572f/preshed-3.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5c8462472f790c16708306aef3a102a762bd19dfe3d2f8ee08bd5e12f51b835", size = 839913, upload-time = "2026-03-23T08:56:49.937Z" }, - { url = "https://files.pythonhosted.org/packages/cf/65/b13f01329decc44ef53cfb6b4601ba85382dcb2a4ec78d9250f03a418066/preshed-3.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c046736239cc8d72670749b79b526e4111839a2fc461a58545d212797649129c", size = 1816452, upload-time = "2026-03-23T08:56:51.233Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c7/f1a996c6832234efd4d543041b582418d41ac480ee55c557ec9e65344637/preshed-3.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7c333f18e9a81c8a6de0603fd8781e17115324b117c445ca91abdf7bfb1abe49", size = 1888978, upload-time = "2026-03-23T08:56:52.591Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b9/96fb71499049885ce19545903fdd38877bbc2be0da47e37c04d01f3e9f66/preshed-3.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:461327f8dd36520dcf1fd55a671e0c3c2c97a2d95e22fc85faa31173f4785dda", size = 122134, upload-time = "2026-03-23T08:56:54.392Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a7/32a4903019d936a2316fdd330bedddac287ac26326107d24fb76a1fbc60a/preshed-3.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:35d6c5acb3ee3b12b87a551913063f0cec784055c2af16e028c19fe875f079d0", size = 108497, upload-time = "2026-03-23T08:56:55.816Z" }, - { url = "https://files.pythonhosted.org/packages/bb/b5/993886c98f5caaa6f07a648cac97a7c62a3093091cad65e1e43a1bd41cc4/preshed-3.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2f1efae396cadab5f3890a2fd43d2ee65373ef9096ccbb805e51e8d8bcc563b", size = 137882, upload-time = "2026-03-23T08:56:56.878Z" }, - { url = "https://files.pythonhosted.org/packages/c6/86/b7fd137cbf140afd6c45e895946068a15f5b55642916de0075e6eb18581c/preshed-3.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d6acc1f5031a535a55a6f7148e2f274554a8343a16309c700cebea0fe7aee8c", size = 138233, upload-time = "2026-03-23T08:56:58.318Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/21a7e79625614134273dfed32bca5bb4c2ec1313e33fbd12d41657536f1f/preshed-3.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da9d931e7660dcdd757e5870269f0c159126d682ed73ed313971d199eb0f334", size = 834835, upload-time = "2026-03-23T08:56:59.48Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3a/2dbd299516461831ae90e0d5b0637137bf28520c4e6dd0b01d6f1886659a/preshed-3.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4ae5cfe075bb7a07982e382bca44f41ddf041f4d24cbd358e8cccfc049259b8", size = 834928, upload-time = "2026-03-23T08:57:01.075Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d3/af654eba4f6587c4ee02c5043e62c194b0a1c4431ffef0c67b9518f6b61c/preshed-3.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7557963d0125a3a7bcdb2eb6948f3e45da31b5a7f066b55320de3dea22d7557f", size = 1820368, upload-time = "2026-03-23T08:57:02.351Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/ebcb2b9e8cb881e40b55b0bf450f8a6b187e2ef3ae0c685cce81d2d85026/preshed-3.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c4bc60dc994864095d784b7e4d77dba3e64188d169ac88722b699d175561fddb", size = 1888251, upload-time = "2026-03-23T08:57:04.158Z" }, - { url = "https://files.pythonhosted.org/packages/97/f7/c6c012779edcaa6e2cd092c554e98dc53e77f41205b07208655ba77e2327/preshed-3.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:208dcebbe294bf1881ce33fb015d56ab2a7587aece85a09147727174207892e4", size = 125211, upload-time = "2026-03-23T08:57:05.83Z" }, - { url = "https://files.pythonhosted.org/packages/f8/82/390ef87d732ef64e673ef6bf9e5d898453986e979efa50fb3a400e2c0766/preshed-3.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:cf8e1a7a1823b2a7765121446c630140ac6e8650c07a6efbf375e168d1fef4f7", size = 111942, upload-time = "2026-03-23T08:57:06.996Z" }, - { url = "https://files.pythonhosted.org/packages/80/3a/a9dde3167bcecb27ae82ce4567b5ab1aa3989113ae6814c092ce223cc4ef/preshed-3.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9ca43ecbc3783eda4d6ab3416ae2ecd9ef23dca5f53995843f69f7457bcd0677", size = 144997, upload-time = "2026-03-23T08:57:08.064Z" }, - { url = "https://files.pythonhosted.org/packages/74/d4/22d9355b50b6a13b407dcad0a81df83fb1d5602092d1f05834674dde8fda/preshed-3.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c8596e41a258ff213553a441e0bb3eb388fd8158e84a7bf3aae6d8ede2c166d3", size = 147294, upload-time = "2026-03-23T08:57:09.411Z" }, - { url = "https://files.pythonhosted.org/packages/70/42/a225ee83fdb306d2a503f21a627953b820f4e079c90c8a84338957cb8ff5/preshed-3.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f8856ca3d88e9b250630d70abb4f260d8933151ddfb413024784b25b009868e", size = 952110, upload-time = "2026-03-23T08:57:10.592Z" }, - { url = "https://files.pythonhosted.org/packages/40/ba/09a9dfe3d22d7e745483fd5d7f2a82cd4d39c161f7d2daa0faa4bd6402be/preshed-3.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e5b2865aecbd2e1e10e5d19bb8bfad765863c1307c6c3e51f2a08bd64122409", size = 932217, upload-time = "2026-03-23T08:57:12.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/5c/e10e2e05133e7fcbd7c40536af1148c82dd24357b8f5726e2c7bc51cfd53/preshed-3.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09f96b477c987755b3c945df214ea1c1c80bfb350e9f34e78da89585535b77e8", size = 1896542, upload-time = "2026-03-23T08:57:13.525Z" }, - { url = "https://files.pythonhosted.org/packages/37/aa/51e5b4109a4cdfae28c3613eeeb10764a3794ebef8de93ffbb109465bea3/preshed-3.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:670db59a52e1823b5f088c764df474e65b686592d4093adbeef14581c95ee2cb", size = 1959473, upload-time = "2026-03-23T08:57:15.706Z" }, - { url = "https://files.pythonhosted.org/packages/0e/6a/1d966f367a14c703dde629d150d996c1b727d442f620300b21c9ec1a24d1/preshed-3.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:b03e21b0bf95eb56e23973f32cabb930e94f352228652f81c0955dbd6967d904", size = 146229, upload-time = "2026-03-23T08:57:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/22/80/368139067603e590a000122355f9c8576c8ebed4fb0b8849feaa2698489d/preshed-3.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:b980f3ea9bb74b7f94464bc3d6eb3c9162b6b79b531febd14c6465c24344d2cc", size = 119339, upload-time = "2026-03-23T08:57:18.882Z" }, + { url = "https://files.pythonhosted.org/packages/39/fb/ccff23c44c04088c248539005fcda78b9014512a34d170c5360f02ad908b/preshed-3.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d14eea14bd01291388928991d7df7d60b9fd19ae970e55006eb4d29b0c1e8eb", size = 138497 }, + { url = "https://files.pythonhosted.org/packages/8e/ce/cad5a8145881a771e6c0d002f2e585fc19b962f120860b54d32af5baa342/preshed-3.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f05b08ce92399c0655b5e0eb5a1cc1f9e295703ed3aabdfaf6538dfa8ae23d57", size = 138010 }, + { url = "https://files.pythonhosted.org/packages/a7/a2/c5fed4fb3e946699259d11e4036a3cfdd8c89b3e542e3077d46781642425/preshed-3.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62cf7f3113132891d6bba70ff547ad81c6fe50a31930bbbb8499f1d47cd122b7", size = 861498 }, + { url = "https://files.pythonhosted.org/packages/51/94/8c9bc48a6ea4903f53a1a0031ce8e35687526949f25821762ef21493c007/preshed-3.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b8de3f58043070a354477995acdd98626ce43e4193c708ebd0f694e467f5155", size = 868988 }, + { url = "https://files.pythonhosted.org/packages/b6/df/ecd2f40055ff52527ca117ffbfafb888c1a3079b59fbabe03c5b8f9b7240/preshed-3.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:183b339956a9e1d7a4a00038a3b9587a734db9e8bd915939a49791bd1b372156", size = 1847382 }, + { url = "https://files.pythonhosted.org/packages/e6/88/bdb244e40284ded3632a9f88c23bc80230bd7b2ae4a8b7f2cc91adead7a8/preshed-3.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e77bed56aded7cbe5d28d6bd2178bc5b13eda0e0e464dab205fb578fa915000", size = 1919236 }, + { url = "https://files.pythonhosted.org/packages/a0/c9/c91ea56342e6c364fc69b444a1ac5432327857199c44032c9cc9dc4c3a23/preshed-3.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:04d8f13f2986e5d11af5ac51f55ce3106c70c41b483d20ea392e6180bdd0f870", size = 122938 }, + { url = "https://files.pythonhosted.org/packages/b2/0b/6a99d99619fd83b14c696e2489caed7070647488d4d3ac0b723d35db2de0/preshed-3.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:19318dc1cd8cac6663c6c830bf7e0002d2de853769fb03e056774e97c21bedfd", size = 109194 }, + { url = "https://files.pythonhosted.org/packages/0e/2a/401158195d6dc7f6aef0b354d74d0e95c9da124499448c2b3dbb95b71204/preshed-3.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0d0c14187dc0078d8a63bf190ec045a4d13e7748b6caeb557a7d575e411410b", size = 137289 }, + { url = "https://files.pythonhosted.org/packages/88/8f/e20e64573988528785447a6893b2e7ab287ecfd85b3888e978b28812fd20/preshed-3.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7770987c2e57497cd26124a9be5f652b5b3ccd0def89859ab0da8bca6144a3de", size = 136847 }, + { url = "https://files.pythonhosted.org/packages/b9/72/18168f881359c4482d312f8dc196371bdd61c1583a52b34390da4c88bbea/preshed-3.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a7bc48220de579be6bdb0a8715482cf36e2a625a6fd5ad26c9f43485a4a23b5", size = 831478 }, + { url = "https://files.pythonhosted.org/packages/fd/3a/3543476091087102775568cea9885dde3453569e9aeee365809108de572f/preshed-3.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5c8462472f790c16708306aef3a102a762bd19dfe3d2f8ee08bd5e12f51b835", size = 839913 }, + { url = "https://files.pythonhosted.org/packages/cf/65/b13f01329decc44ef53cfb6b4601ba85382dcb2a4ec78d9250f03a418066/preshed-3.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c046736239cc8d72670749b79b526e4111839a2fc461a58545d212797649129c", size = 1816452 }, + { url = "https://files.pythonhosted.org/packages/d1/c7/f1a996c6832234efd4d543041b582418d41ac480ee55c557ec9e65344637/preshed-3.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7c333f18e9a81c8a6de0603fd8781e17115324b117c445ca91abdf7bfb1abe49", size = 1888978 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/96fb71499049885ce19545903fdd38877bbc2be0da47e37c04d01f3e9f66/preshed-3.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:461327f8dd36520dcf1fd55a671e0c3c2c97a2d95e22fc85faa31173f4785dda", size = 122134 }, + { url = "https://files.pythonhosted.org/packages/ef/a7/32a4903019d936a2316fdd330bedddac287ac26326107d24fb76a1fbc60a/preshed-3.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:35d6c5acb3ee3b12b87a551913063f0cec784055c2af16e028c19fe875f079d0", size = 108497 }, + { url = "https://files.pythonhosted.org/packages/bb/b5/993886c98f5caaa6f07a648cac97a7c62a3093091cad65e1e43a1bd41cc4/preshed-3.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2f1efae396cadab5f3890a2fd43d2ee65373ef9096ccbb805e51e8d8bcc563b", size = 137882 }, + { url = "https://files.pythonhosted.org/packages/c6/86/b7fd137cbf140afd6c45e895946068a15f5b55642916de0075e6eb18581c/preshed-3.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d6acc1f5031a535a55a6f7148e2f274554a8343a16309c700cebea0fe7aee8c", size = 138233 }, + { url = "https://files.pythonhosted.org/packages/8b/ca/21a7e79625614134273dfed32bca5bb4c2ec1313e33fbd12d41657536f1f/preshed-3.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da9d931e7660dcdd757e5870269f0c159126d682ed73ed313971d199eb0f334", size = 834835 }, + { url = "https://files.pythonhosted.org/packages/8f/3a/2dbd299516461831ae90e0d5b0637137bf28520c4e6dd0b01d6f1886659a/preshed-3.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4ae5cfe075bb7a07982e382bca44f41ddf041f4d24cbd358e8cccfc049259b8", size = 834928 }, + { url = "https://files.pythonhosted.org/packages/7c/d3/af654eba4f6587c4ee02c5043e62c194b0a1c4431ffef0c67b9518f6b61c/preshed-3.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7557963d0125a3a7bcdb2eb6948f3e45da31b5a7f066b55320de3dea22d7557f", size = 1820368 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/ebcb2b9e8cb881e40b55b0bf450f8a6b187e2ef3ae0c685cce81d2d85026/preshed-3.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c4bc60dc994864095d784b7e4d77dba3e64188d169ac88722b699d175561fddb", size = 1888251 }, + { url = "https://files.pythonhosted.org/packages/97/f7/c6c012779edcaa6e2cd092c554e98dc53e77f41205b07208655ba77e2327/preshed-3.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:208dcebbe294bf1881ce33fb015d56ab2a7587aece85a09147727174207892e4", size = 125211 }, + { url = "https://files.pythonhosted.org/packages/f8/82/390ef87d732ef64e673ef6bf9e5d898453986e979efa50fb3a400e2c0766/preshed-3.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:cf8e1a7a1823b2a7765121446c630140ac6e8650c07a6efbf375e168d1fef4f7", size = 111942 }, + { url = "https://files.pythonhosted.org/packages/80/3a/a9dde3167bcecb27ae82ce4567b5ab1aa3989113ae6814c092ce223cc4ef/preshed-3.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9ca43ecbc3783eda4d6ab3416ae2ecd9ef23dca5f53995843f69f7457bcd0677", size = 144997 }, + { url = "https://files.pythonhosted.org/packages/74/d4/22d9355b50b6a13b407dcad0a81df83fb1d5602092d1f05834674dde8fda/preshed-3.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c8596e41a258ff213553a441e0bb3eb388fd8158e84a7bf3aae6d8ede2c166d3", size = 147294 }, + { url = "https://files.pythonhosted.org/packages/70/42/a225ee83fdb306d2a503f21a627953b820f4e079c90c8a84338957cb8ff5/preshed-3.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f8856ca3d88e9b250630d70abb4f260d8933151ddfb413024784b25b009868e", size = 952110 }, + { url = "https://files.pythonhosted.org/packages/40/ba/09a9dfe3d22d7e745483fd5d7f2a82cd4d39c161f7d2daa0faa4bd6402be/preshed-3.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e5b2865aecbd2e1e10e5d19bb8bfad765863c1307c6c3e51f2a08bd64122409", size = 932217 }, + { url = "https://files.pythonhosted.org/packages/6c/5c/e10e2e05133e7fcbd7c40536af1148c82dd24357b8f5726e2c7bc51cfd53/preshed-3.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09f96b477c987755b3c945df214ea1c1c80bfb350e9f34e78da89585535b77e8", size = 1896542 }, + { url = "https://files.pythonhosted.org/packages/37/aa/51e5b4109a4cdfae28c3613eeeb10764a3794ebef8de93ffbb109465bea3/preshed-3.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:670db59a52e1823b5f088c764df474e65b686592d4093adbeef14581c95ee2cb", size = 1959473 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/1d966f367a14c703dde629d150d996c1b727d442f620300b21c9ec1a24d1/preshed-3.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:b03e21b0bf95eb56e23973f32cabb930e94f352228652f81c0955dbd6967d904", size = 146229 }, + { url = "https://files.pythonhosted.org/packages/22/80/368139067603e590a000122355f9c8576c8ebed4fb0b8849feaa2698489d/preshed-3.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:b980f3ea9bb74b7f94464bc3d6eb3c9162b6b79b531febd14c6465c24344d2cc", size = 119339 }, ] [[package]] name = "progress" version = "1.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/26/3b086f0c5d6c1c18c2430d6fac3a99d79553884ca6cdf759cf256dd43b7d/progress-1.6.1.tar.gz", hash = "sha256:c1ba719f862ce885232a759eab47971fe74dfc7bb76ab8a51ef5940bad35086c", size = 7164, upload-time = "2025-07-01T05:50:43.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/26/3b086f0c5d6c1c18c2430d6fac3a99d79553884ca6cdf759cf256dd43b7d/progress-1.6.1.tar.gz", hash = "sha256:c1ba719f862ce885232a759eab47971fe74dfc7bb76ab8a51ef5940bad35086c", size = 7164 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/59/123aee44a039b212cfb8d90be1adf06496a99b313ee1683aadf90b3d9799/progress-1.6.1-py3-none-any.whl", hash = "sha256:5239f22f305c12fdc8ce6e0e47f70f21622a935e16eafc4535617112e7c7ea0b", size = 9761, upload-time = "2025-07-01T05:50:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/123aee44a039b212cfb8d90be1adf06496a99b313ee1683aadf90b3d9799/progress-1.6.1-py3-none-any.whl", hash = "sha256:5239f22f305c12fdc8ce6e0e47f70f21622a935e16eafc4535617112e7c7ea0b", size = 9761 }, ] [[package]] name = "prometheus-client" version = "0.24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616 } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057 }, ] [[package]] @@ -6026,93 +6018,93 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 }, ] [[package]] name = "propcache" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, - { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, - { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, - { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, - { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, - { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, - { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, - { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, - { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, - { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061 }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037 }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324 }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505 }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242 }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474 }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575 }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736 }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019 }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376 }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988 }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615 }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066 }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655 }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789 }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750 }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780 }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308 }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182 }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215 }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112 }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442 }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398 }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920 }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748 }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877 }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437 }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586 }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790 }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158 }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451 }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374 }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396 }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950 }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856 }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420 }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254 }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205 }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873 }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739 }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514 }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781 }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396 }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897 }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789 }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152 }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869 }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596 }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981 }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490 }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371 }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424 }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566 }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130 }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625 }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209 }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797 }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140 }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257 }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097 }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455 }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372 }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411 }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712 }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557 }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015 }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880 }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938 }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641 }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510 }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161 }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393 }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546 }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259 }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428 }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305 }, ] [[package]] @@ -6122,52 +6114,52 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" }, + { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450 }, ] [[package]] name = "protobuf" version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, - { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, - { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, - { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, - { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739 }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089 }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737 }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610 }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381 }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436 }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656 }, ] [[package]] name = "psutil" version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595 }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082 }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476 }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062 }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893 }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589 }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664 }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087 }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383 }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210 }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228 }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284 }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090 }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859 }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560 }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997 }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972 }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266 }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737 }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617 }, ] [[package]] @@ -6178,9 +6170,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768 }, ] [package.optional-dependencies] @@ -6196,39 +6188,39 @@ name = "psycopg-binary" version = "3.3.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170, upload-time = "2026-02-18T16:48:55.594Z" }, - { url = "https://files.pythonhosted.org/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009, upload-time = "2026-02-18T16:49:03.608Z" }, - { url = "https://files.pythonhosted.org/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735, upload-time = "2026-02-18T16:49:10.128Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919, upload-time = "2026-02-18T16:49:16.335Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785, upload-time = "2026-02-18T16:49:22.687Z" }, - { url = "https://files.pythonhosted.org/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008, upload-time = "2026-02-18T16:49:27.313Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255, upload-time = "2026-02-18T16:49:31.575Z" }, - { url = "https://files.pythonhosted.org/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166, upload-time = "2026-02-18T16:49:35.801Z" }, - { url = "https://files.pythonhosted.org/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544, upload-time = "2026-02-18T16:49:41.129Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297, upload-time = "2026-02-18T16:49:46.726Z" }, - { url = "https://files.pythonhosted.org/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659, upload-time = "2026-02-18T16:49:52.999Z" }, - { url = "https://files.pythonhosted.org/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" }, - { url = "https://files.pythonhosted.org/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" }, - { url = "https://files.pythonhosted.org/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" }, - { url = "https://files.pythonhosted.org/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" }, - { url = "https://files.pythonhosted.org/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" }, - { url = "https://files.pythonhosted.org/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" }, - { url = "https://files.pythonhosted.org/packages/a2/71/7a57e5b12275fe7e7d84d54113f0226080423a869118419c9106c083a21c/psycopg_binary-3.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:497852c5eaf1f0c2d88ab74a64a8097c099deac0c71de1cbcf18659a8a04a4b2", size = 4607368, upload-time = "2026-02-18T16:51:19.295Z" }, - { url = "https://files.pythonhosted.org/packages/c7/04/cb834f120f2b2c10d4003515ef9ca9d688115b9431735e3936ae48549af8/psycopg_binary-3.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:258d1ea53464d29768bf25930f43291949f4c7becc706f6e220c515a63a24edd", size = 4687047, upload-time = "2026-02-18T16:51:23.84Z" }, - { url = "https://files.pythonhosted.org/packages/40/e9/47a69692d3da9704468041aa5ed3ad6fc7f6bb1a5ae788d261a26bbca6c7/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:111c59897a452196116db12e7f608da472fbff000693a21040e35fc978b23430", size = 5487096, upload-time = "2026-02-18T16:51:29.645Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b6/0e0dd6a2f802864a4ae3dbadf4ec620f05e3904c7842b326aafc43e5f464/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17bb6600e2455993946385249a3c3d0af52cd70c1c1cdbf712e9d696d0b0bf1b", size = 5168720, upload-time = "2026-02-18T16:51:36.499Z" }, - { url = "https://files.pythonhosted.org/packages/6f/0d/977af38ac19a6b55d22dff508bd743fd7c1901e1b73657e7937c7cccb0a3/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642050398583d61c9856210568eb09a8e4f2fe8224bf3be21b67a370e677eead", size = 6762076, upload-time = "2026-02-18T16:51:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/34/40/912a39d48322cf86895c0eaf2d5b95cb899402443faefd4b09abbba6b6e1/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:533efe6dc3a7cba5e2a84e38970786bb966306863e45f3db152007e9f48638a6", size = 4997623, upload-time = "2026-02-18T16:51:47.707Z" }, - { url = "https://files.pythonhosted.org/packages/98/0c/c14d0e259c65dc7be854d926993f151077887391d5a081118907a9d89603/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5958dbf28b77ce2033482f6cb9ef04d43f5d8f4b7636e6963d5626f000efb23e", size = 4532096, upload-time = "2026-02-18T16:51:51.421Z" }, - { url = "https://files.pythonhosted.org/packages/39/21/8b7c50a194cfca6ea0fd4d1f276158307785775426e90700ab2eba5cd623/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a6af77b6626ce92b5817bf294b4d45ec1a6161dba80fc2d82cdffdd6814fd023", size = 4208884, upload-time = "2026-02-18T16:51:57.336Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2c/a4981bf42cf30ebba0424971d7ce70a222ae9b82594c42fc3f2105d7b525/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:47f06fcbe8542b4d96d7392c476a74ada521c5aebdb41c3c0155f6595fc14c8d", size = 3944542, upload-time = "2026-02-18T16:52:04.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/e9/b7c29b56aa0b85a4e0c4d89db691c1ceef08f46a356369144430c155a2f5/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7800e6c6b5dc4b0ca7cc7370f770f53ac83886b76afda0848065a674231e856", size = 4254339, upload-time = "2026-02-18T16:52:10.444Z" }, - { url = "https://files.pythonhosted.org/packages/98/5a/291d89f44d3820fffb7a04ebc8f3ef5dda4f542f44a5daea0c55a84abf45/psycopg_binary-3.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:165f22ab5a9513a3d7425ffb7fcc7955ed8ccaeef6d37e369d6cc1dff1582383", size = 3652796, upload-time = "2026-02-18T16:52:14.02Z" }, + { url = "https://files.pythonhosted.org/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170 }, + { url = "https://files.pythonhosted.org/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009 }, + { url = "https://files.pythonhosted.org/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735 }, + { url = "https://files.pythonhosted.org/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919 }, + { url = "https://files.pythonhosted.org/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785 }, + { url = "https://files.pythonhosted.org/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008 }, + { url = "https://files.pythonhosted.org/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255 }, + { url = "https://files.pythonhosted.org/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166 }, + { url = "https://files.pythonhosted.org/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544 }, + { url = "https://files.pythonhosted.org/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297 }, + { url = "https://files.pythonhosted.org/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659 }, + { url = "https://files.pythonhosted.org/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414 }, + { url = "https://files.pythonhosted.org/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021 }, + { url = "https://files.pythonhosted.org/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453 }, + { url = "https://files.pythonhosted.org/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135 }, + { url = "https://files.pythonhosted.org/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315 }, + { url = "https://files.pythonhosted.org/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783 }, + { url = "https://files.pythonhosted.org/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023 }, + { url = "https://files.pythonhosted.org/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874 }, + { url = "https://files.pythonhosted.org/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668 }, + { url = "https://files.pythonhosted.org/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973 }, + { url = "https://files.pythonhosted.org/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342 }, + { url = "https://files.pythonhosted.org/packages/a2/71/7a57e5b12275fe7e7d84d54113f0226080423a869118419c9106c083a21c/psycopg_binary-3.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:497852c5eaf1f0c2d88ab74a64a8097c099deac0c71de1cbcf18659a8a04a4b2", size = 4607368 }, + { url = "https://files.pythonhosted.org/packages/c7/04/cb834f120f2b2c10d4003515ef9ca9d688115b9431735e3936ae48549af8/psycopg_binary-3.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:258d1ea53464d29768bf25930f43291949f4c7becc706f6e220c515a63a24edd", size = 4687047 }, + { url = "https://files.pythonhosted.org/packages/40/e9/47a69692d3da9704468041aa5ed3ad6fc7f6bb1a5ae788d261a26bbca6c7/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:111c59897a452196116db12e7f608da472fbff000693a21040e35fc978b23430", size = 5487096 }, + { url = "https://files.pythonhosted.org/packages/0b/b6/0e0dd6a2f802864a4ae3dbadf4ec620f05e3904c7842b326aafc43e5f464/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17bb6600e2455993946385249a3c3d0af52cd70c1c1cdbf712e9d696d0b0bf1b", size = 5168720 }, + { url = "https://files.pythonhosted.org/packages/6f/0d/977af38ac19a6b55d22dff508bd743fd7c1901e1b73657e7937c7cccb0a3/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642050398583d61c9856210568eb09a8e4f2fe8224bf3be21b67a370e677eead", size = 6762076 }, + { url = "https://files.pythonhosted.org/packages/34/40/912a39d48322cf86895c0eaf2d5b95cb899402443faefd4b09abbba6b6e1/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:533efe6dc3a7cba5e2a84e38970786bb966306863e45f3db152007e9f48638a6", size = 4997623 }, + { url = "https://files.pythonhosted.org/packages/98/0c/c14d0e259c65dc7be854d926993f151077887391d5a081118907a9d89603/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5958dbf28b77ce2033482f6cb9ef04d43f5d8f4b7636e6963d5626f000efb23e", size = 4532096 }, + { url = "https://files.pythonhosted.org/packages/39/21/8b7c50a194cfca6ea0fd4d1f276158307785775426e90700ab2eba5cd623/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a6af77b6626ce92b5817bf294b4d45ec1a6161dba80fc2d82cdffdd6814fd023", size = 4208884 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/a4981bf42cf30ebba0424971d7ce70a222ae9b82594c42fc3f2105d7b525/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:47f06fcbe8542b4d96d7392c476a74ada521c5aebdb41c3c0155f6595fc14c8d", size = 3944542 }, + { url = "https://files.pythonhosted.org/packages/60/e9/b7c29b56aa0b85a4e0c4d89db691c1ceef08f46a356369144430c155a2f5/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7800e6c6b5dc4b0ca7cc7370f770f53ac83886b76afda0848065a674231e856", size = 4254339 }, + { url = "https://files.pythonhosted.org/packages/98/5a/291d89f44d3820fffb7a04ebc8f3ef5dda4f542f44a5daea0c55a84abf45/psycopg_binary-3.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:165f22ab5a9513a3d7425ffb7fcc7955ed8ccaeef6d37e369d6cc1dff1582383", size = 3652796 }, ] [[package]] @@ -6238,59 +6230,59 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606, upload-time = "2025-12-01T11:34:33.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995, upload-time = "2025-12-01T11:34:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995 }, ] [[package]] name = "psycopg2-binary" version = "2.9.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, - { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, - { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, - { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, - { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, - { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, - { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, - { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, - { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, - { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, - { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, - { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, - { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, - { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, - { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603 }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509 }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159 }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234 }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236 }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083 }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281 }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010 }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641 }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940 }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147 }, + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572 }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529 }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242 }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258 }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295 }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133 }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383 }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168 }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712 }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549 }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215 }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567 }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755 }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646 }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701 }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293 }, + { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184 }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650 }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663 }, + { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737 }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643 }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913 }, ] [[package]] name = "pwdlib" version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810, upload-time = "2025-10-25T12:44:24.395Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633, upload-time = "2025-10-25T12:44:23.406Z" }, + { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633 }, ] [package.optional-dependencies] @@ -6305,37 +6297,37 @@ bcrypt = [ name = "pyarrow" version = "18.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671, upload-time = "2024-11-26T02:01:48.62Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620, upload-time = "2024-11-26T01:59:39.797Z" }, - { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494, upload-time = "2024-11-26T01:59:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624, upload-time = "2024-11-26T01:59:49.189Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341, upload-time = "2024-11-26T01:59:54.849Z" }, - { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629, upload-time = "2024-11-26T01:59:59.966Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661, upload-time = "2024-11-26T02:00:04.55Z" }, - { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330, upload-time = "2024-11-26T02:00:09.576Z" }, - { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406, upload-time = "2024-11-26T02:00:14.469Z" }, - { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095, upload-time = "2024-11-26T02:00:19.347Z" }, - { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527, upload-time = "2024-11-26T02:00:24.085Z" }, - { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443, upload-time = "2024-11-26T02:00:29.483Z" }, - { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750, upload-time = "2024-11-26T02:00:34.069Z" }, - { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690, upload-time = "2024-11-26T02:00:39.603Z" }, - { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054, upload-time = "2024-11-26T02:00:43.611Z" }, - { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542, upload-time = "2024-11-26T02:00:48.094Z" }, - { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412, upload-time = "2024-11-26T02:00:52.458Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106, upload-time = "2024-11-26T02:00:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940, upload-time = "2024-11-26T02:01:02.31Z" }, - { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177, upload-time = "2024-11-26T02:01:07.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567, upload-time = "2024-11-26T02:01:12.931Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, + { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, + { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341 }, + { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629 }, + { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661 }, + { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330 }, + { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406 }, + { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095 }, + { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527 }, + { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443 }, + { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750 }, + { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690 }, + { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054 }, + { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542 }, + { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412 }, + { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106 }, + { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940 }, + { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177 }, + { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, ] [[package]] name = "pyasn1" version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997 }, ] [[package]] @@ -6345,166 +6337,161 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, ] [[package]] name = "pybase64" version = "1.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167, upload-time = "2025-12-06T13:23:16.821Z" }, - { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673, upload-time = "2025-12-06T13:23:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210, upload-time = "2025-12-06T13:23:18.813Z" }, - { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599, upload-time = "2025-12-06T13:23:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922, upload-time = "2025-12-06T13:23:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712, upload-time = "2025-12-06T13:23:22.77Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300, upload-time = "2025-12-06T13:23:24.543Z" }, - { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278, upload-time = "2025-12-06T13:23:25.608Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817, upload-time = "2025-12-06T13:23:26.633Z" }, - { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611, upload-time = "2025-12-06T13:23:27.684Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404, upload-time = "2025-12-06T13:23:28.714Z" }, - { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817, upload-time = "2025-12-06T13:23:30.079Z" }, - { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854, upload-time = "2025-12-06T13:23:31.17Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333, upload-time = "2025-12-06T13:23:32.422Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492, upload-time = "2025-12-06T13:23:33.515Z" }, - { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974, upload-time = "2025-12-06T13:23:36.27Z" }, - { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" }, - { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" }, - { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901, upload-time = "2025-12-06T13:23:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807, upload-time = "2025-12-06T13:23:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" }, - { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" }, - { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158, upload-time = "2025-12-06T13:23:46.872Z" }, - { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672, upload-time = "2025-12-06T13:23:47.88Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244, upload-time = "2025-12-06T13:23:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620, upload-time = "2025-12-06T13:23:50.081Z" }, - { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930, upload-time = "2025-12-06T13:23:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425, upload-time = "2025-12-06T13:23:52.732Z" }, - { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327, upload-time = "2025-12-06T13:23:53.856Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294, upload-time = "2025-12-06T13:23:54.937Z" }, - { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858, upload-time = "2025-12-06T13:23:56.031Z" }, - { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629, upload-time = "2025-12-06T13:23:57.12Z" }, - { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448, upload-time = "2025-12-06T13:23:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841, upload-time = "2025-12-06T13:23:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910, upload-time = "2025-12-06T13:24:00.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335, upload-time = "2025-12-06T13:24:02.046Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486, upload-time = "2025-12-06T13:24:03.141Z" }, - { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978, upload-time = "2025-12-06T13:24:04.221Z" }, - { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" }, - { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497, upload-time = "2025-12-06T13:24:08.873Z" }, - { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076, upload-time = "2025-12-06T13:24:09.975Z" }, - { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317, upload-time = "2025-12-06T13:24:11.129Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534, upload-time = "2025-12-06T13:24:12.433Z" }, - { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399, upload-time = "2025-12-06T13:24:13.928Z" }, - { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487, upload-time = "2025-12-06T13:24:15.177Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959, upload-time = "2025-12-06T13:24:16.854Z" }, - { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874, upload-time = "2025-12-06T13:24:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572, upload-time = "2025-12-06T13:24:19.417Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636, upload-time = "2025-12-06T13:24:20.497Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193, upload-time = "2025-12-06T13:24:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655, upload-time = "2025-12-06T13:24:22.673Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471, upload-time = "2025-12-06T13:24:23.8Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119, upload-time = "2025-12-06T13:24:24.994Z" }, - { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791, upload-time = "2025-12-06T13:24:26.046Z" }, - { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701, upload-time = "2025-12-06T13:24:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" }, - { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" }, - { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835, upload-time = "2025-12-06T13:24:31.767Z" }, - { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673, upload-time = "2025-12-06T13:24:32.82Z" }, - { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" }, - { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257, upload-time = "2025-12-06T13:24:38.049Z" }, - { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685, upload-time = "2025-12-06T13:24:40.327Z" }, - { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460, upload-time = "2025-12-06T13:24:41.735Z" }, - { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688, upload-time = "2025-12-06T13:24:42.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040, upload-time = "2025-12-06T13:24:44.37Z" }, - { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478, upload-time = "2025-12-06T13:24:45.815Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463, upload-time = "2025-12-06T13:24:46.938Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360, upload-time = "2025-12-06T13:24:48.039Z" }, - { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999, upload-time = "2025-12-06T13:24:49.547Z" }, - { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736, upload-time = "2025-12-06T13:24:50.641Z" }, - { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298, upload-time = "2025-12-06T13:24:51.791Z" }, - { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049, upload-time = "2025-12-06T13:24:53.253Z" }, - { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952, upload-time = "2025-12-06T13:24:54.342Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484, upload-time = "2025-12-06T13:24:55.774Z" }, - { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542, upload-time = "2025-12-06T13:24:57Z" }, - { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045, upload-time = "2025-12-06T13:24:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601, upload-time = "2025-12-06T13:25:04.222Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078, upload-time = "2025-12-06T13:25:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474, upload-time = "2025-12-06T13:25:06.434Z" }, - { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706, upload-time = "2025-12-06T13:25:07.636Z" }, - { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589, upload-time = "2025-12-06T13:25:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670, upload-time = "2025-12-06T13:25:10.04Z" }, - { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194, upload-time = "2025-12-06T13:25:11.48Z" }, - { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984, upload-time = "2025-12-06T13:25:12.645Z" }, - { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750, upload-time = "2025-12-06T13:25:13.848Z" }, - { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816, upload-time = "2025-12-06T13:25:15.356Z" }, - { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348, upload-time = "2025-12-06T13:25:16.559Z" }, - { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842, upload-time = "2025-12-06T13:25:18.055Z" }, - { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651, upload-time = "2025-12-06T13:25:19.191Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295, upload-time = "2025-12-06T13:25:20.822Z" }, - { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960, upload-time = "2025-12-06T13:25:22.099Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863, upload-time = "2025-12-06T13:25:23.442Z" }, - { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" }, - { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" }, - { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" }, - { url = "https://files.pythonhosted.org/packages/17/45/92322aec1b6979e789b5710f73c59f2172bc37c8ce835305434796824b7b/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:2baaa092f3475f3a9c87ac5198023918ea8b6c125f4c930752ab2cbe3cd1d520", size = 38746, upload-time = "2025-12-06T13:26:25.869Z" }, - { url = "https://files.pythonhosted.org/packages/11/94/f1a07402870388fdfc2ecec0c718111189732f7d0f2d7fe1386e19e8fad0/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:cde13c0764b1af07a631729f26df019070dad759981d6975527b7e8ecb465b6c", size = 32573, upload-time = "2025-12-06T13:26:27.792Z" }, - { url = "https://files.pythonhosted.org/packages/fa/8f/43c3bb11ca9bacf81cb0b7a71500bb65b2eda6d5fe07433c09b543de97f3/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5c29a582b0ea3936d02bd6fe9bf674ab6059e6e45ab71c78404ab2c913224414", size = 43461, upload-time = "2025-12-06T13:26:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4c/2a5258329200be57497d3972b5308558c6de42e3749c6cc2aa1cbe34b25a/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6b664758c804fa919b4f1257aa8cf68e95db76fc331de5f70bfc3a34655afe1", size = 36058, upload-time = "2025-12-06T13:26:30.092Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167 }, + { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673 }, + { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210 }, + { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599 }, + { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922 }, + { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712 }, + { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300 }, + { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278 }, + { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817 }, + { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611 }, + { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404 }, + { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817 }, + { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854 }, + { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333 }, + { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492 }, + { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974 }, + { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686 }, + { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833 }, + { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185 }, + { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901 }, + { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807 }, + { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932 }, + { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394 }, + { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078 }, + { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158 }, + { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672 }, + { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244 }, + { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620 }, + { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930 }, + { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425 }, + { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327 }, + { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294 }, + { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858 }, + { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629 }, + { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448 }, + { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841 }, + { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910 }, + { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335 }, + { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486 }, + { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978 }, + { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684 }, + { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832 }, + { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175 }, + { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497 }, + { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076 }, + { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317 }, + { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534 }, + { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399 }, + { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487 }, + { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959 }, + { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874 }, + { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572 }, + { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636 }, + { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193 }, + { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655 }, + { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471 }, + { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119 }, + { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791 }, + { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701 }, + { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965 }, + { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207 }, + { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505 }, + { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835 }, + { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673 }, + { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939 }, + { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401 }, + { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075 }, + { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257 }, + { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685 }, + { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460 }, + { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688 }, + { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040 }, + { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478 }, + { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463 }, + { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360 }, + { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999 }, + { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736 }, + { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298 }, + { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049 }, + { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952 }, + { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484 }, + { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542 }, + { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045 }, + { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200 }, + { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323 }, + { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584 }, + { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601 }, + { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078 }, + { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474 }, + { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706 }, + { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589 }, + { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670 }, + { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194 }, + { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984 }, + { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750 }, + { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816 }, + { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348 }, + { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842 }, + { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651 }, + { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295 }, + { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960 }, + { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863 }, + { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513 }, + { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702 }, + { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904 }, ] [[package]] name = "pyclipper" version = "1.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489, upload-time = "2025-12-01T13:15:35.015Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676, upload-time = "2025-12-01T13:15:04.15Z" }, - { url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458, upload-time = "2025-12-01T13:15:05.76Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235, upload-time = "2025-12-01T13:15:06.993Z" }, - { url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388, upload-time = "2025-12-01T13:15:08.467Z" }, - { url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169, upload-time = "2025-12-01T13:15:10.098Z" }, - { url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619, upload-time = "2025-12-01T13:15:11.592Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342, upload-time = "2025-12-01T13:15:12.697Z" }, - { url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839, upload-time = "2025-12-01T13:15:13.911Z" }, - { url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142, upload-time = "2025-12-01T13:15:15.477Z" }, - { url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789, upload-time = "2025-12-01T13:15:17.042Z" }, - { url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817, upload-time = "2025-12-01T13:15:18.724Z" }, - { url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007, upload-time = "2025-12-01T13:15:19.763Z" }, - { url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167, upload-time = "2025-12-01T13:15:20.844Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966, upload-time = "2025-12-01T13:15:22.036Z" }, - { url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216, upload-time = "2025-12-01T13:15:23.18Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198, upload-time = "2025-12-01T13:15:24.522Z" }, - { url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951, upload-time = "2025-12-01T13:15:25.79Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782, upload-time = "2025-12-01T13:15:26.945Z" }, - { url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880, upload-time = "2025-12-01T13:15:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706, upload-time = "2025-12-01T13:15:30.134Z" }, - { url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308, upload-time = "2025-12-01T13:15:31.225Z" }, - { url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608, upload-time = "2025-12-01T13:15:32.69Z" }, + { url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676 }, + { url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458 }, + { url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235 }, + { url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388 }, + { url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169 }, + { url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619 }, + { url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342 }, + { url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839 }, + { url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142 }, + { url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789 }, + { url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817 }, + { url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007 }, + { url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167 }, + { url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966 }, + { url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216 }, + { url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198 }, + { url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951 }, + { url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782 }, + { url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880 }, + { url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308 }, + { url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608 }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, ] [[package]] @@ -6517,9 +6504,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580 }, ] [[package]] @@ -6529,68 +6516,64 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990 }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003 }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200 }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578 }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504 }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816 }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366 }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698 }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603 }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591 }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068 }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908 }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145 }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179 }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403 }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206 }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258 }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917 }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186 }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146 }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788 }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133 }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852 }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679 }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766 }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005 }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622 }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725 }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040 }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691 }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897 }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302 }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877 }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680 }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960 }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102 }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039 }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126 }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489 }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288 }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255 }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760 }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092 }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385 }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832 }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078 }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914 }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560 }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244 }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955 }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906 }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607 }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769 }, ] [[package]] @@ -6602,9 +6585,9 @@ dependencies = [ { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929 }, ] [[package]] @@ -6614,27 +6597,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659 }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] [[package]] name = "pyjwt" version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726 }, ] [package.optional-dependencies] @@ -6646,7 +6629,7 @@ crypto = [ name = "pylatexenc" version = "2.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597, upload-time = "2021-04-06T07:56:07.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597 } [[package]] name = "pymilvus" @@ -6662,9 +6645,9 @@ dependencies = [ { name = "requests" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/e6/0adc3b374f5c5d1eebd4f551b455c6865c449b170b17545001b208e2b153/pymilvus-2.6.11.tar.gz", hash = "sha256:a40c10322cde25184a8c3d84993a14dfb67ad2bdcfc5dff7e68b11a79ff8f6d8", size = 1583634, upload-time = "2026-03-27T06:25:46.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/e6/0adc3b374f5c5d1eebd4f551b455c6865c449b170b17545001b208e2b153/pymilvus-2.6.11.tar.gz", hash = "sha256:a40c10322cde25184a8c3d84993a14dfb67ad2bdcfc5dff7e68b11a79ff8f6d8", size = 1583634 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/1c/bccb331d71f824738f80f11e9b8b4da47973c903826355526ae4fa2b762f/pymilvus-2.6.11-py3-none-any.whl", hash = "sha256:a11e1718b15045361c71ca671b959900cb7e2faae863c896f6b7e87bf2e4d10a", size = 315252, upload-time = "2026-03-27T06:25:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1c/bccb331d71f824738f80f11e9b8b4da47973c903826355526ae4fa2b762f/pymilvus-2.6.11-py3-none-any.whl", hash = "sha256:a11e1718b15045361c71ca671b959900cb7e2faae863c896f6b7e87bf2e4d10a", size = 315252 }, ] [[package]] @@ -6674,61 +6657,61 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/9c/a4895c4b785fc9865a84a56e14b5bd21ca75aadc3dab79c14187cdca189b/pymongo-4.16.0.tar.gz", hash = "sha256:8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c", size = 2495323, upload-time = "2026-01-07T18:05:48.107Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/9c/a4895c4b785fc9865a84a56e14b5bd21ca75aadc3dab79c14187cdca189b/pymongo-4.16.0.tar.gz", hash = "sha256:8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c", size = 2495323 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/03/6dd7c53cbde98de469a3e6fb893af896dca644c476beb0f0c6342bcc368b/pymongo-4.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd4911c40a43a821dfd93038ac824b756b6e703e26e951718522d29f6eb166a8", size = 917619, upload-time = "2026-01-07T18:04:19.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/e1/328915f2734ea1f355dc9b0e98505ff670f5fab8be5e951d6ed70971c6aa/pymongo-4.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25a6b03a68f9907ea6ec8bc7cf4c58a1b51a18e23394f962a6402f8e46d41211", size = 917364, upload-time = "2026-01-07T18:04:20.861Z" }, - { url = "https://files.pythonhosted.org/packages/41/fe/4769874dd9812a1bc2880a9785e61eba5340da966af888dd430392790ae0/pymongo-4.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:91ac0cb0fe2bf17616c2039dac88d7c9a5088f5cb5829b27c9d250e053664d31", size = 1686901, upload-time = "2026-01-07T18:04:22.219Z" }, - { url = "https://files.pythonhosted.org/packages/fa/8d/15707b9669fdc517bbc552ac60da7124dafe7ac1552819b51e97ed4038b4/pymongo-4.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf0ec79e8ca7077f455d14d915d629385153b6a11abc0b93283ed73a8013e376", size = 1723034, upload-time = "2026-01-07T18:04:24.055Z" }, - { url = "https://files.pythonhosted.org/packages/5b/af/3d5d16ff11d447d40c1472da1b366a31c7380d7ea2922a449c7f7f495567/pymongo-4.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d0082631a7510318befc2b4fdab140481eb4b9dd62d9245e042157085da2a70", size = 1797161, upload-time = "2026-01-07T18:04:25.964Z" }, - { url = "https://files.pythonhosted.org/packages/fb/04/725ab8664eeec73ec125b5a873448d80f5d8cf2750aaaf804cbc538a50a5/pymongo-4.16.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85dc2f3444c346ea019a371e321ac868a4fab513b7a55fe368f0cc78de8177cc", size = 1780938, upload-time = "2026-01-07T18:04:28.745Z" }, - { url = "https://files.pythonhosted.org/packages/22/50/dd7e9095e1ca35f93c3c844c92eb6eb0bc491caeb2c9bff3b32fe3c9b18f/pymongo-4.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbf3c14de75a20cc3c30bf0c6527157224a93dfb605838eabb1a2ee3be008d", size = 1714342, upload-time = "2026-01-07T18:04:30.331Z" }, - { url = "https://files.pythonhosted.org/packages/03/c9/542776987d5c31ae8e93e92680ea2b6e5a2295f398b25756234cabf38a39/pymongo-4.16.0-cp312-cp312-win32.whl", hash = "sha256:60307bb91e0ab44e560fe3a211087748b2b5f3e31f403baf41f5b7b0a70bd104", size = 887868, upload-time = "2026-01-07T18:04:32.124Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d4/b4045a7ccc5680fb496d01edf749c7a9367cc8762fbdf7516cf807ef679b/pymongo-4.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f513b2c6c0d5c491f478422f6b5b5c27ac1af06a54c93ef8631806f7231bd92e", size = 907554, upload-time = "2026-01-07T18:04:33.685Z" }, - { url = "https://files.pythonhosted.org/packages/60/4c/33f75713d50d5247f2258405142c0318ff32c6f8976171c4fcae87a9dbdf/pymongo-4.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:dfc320f08ea9a7ec5b2403dc4e8150636f0d6150f4b9792faaae539c88e7db3b", size = 892971, upload-time = "2026-01-07T18:04:35.594Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/148d8b5da8260f4679d6665196ae04ab14ffdf06f5fe670b0ab11942951f/pymongo-4.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d15f060bc6d0964a8bb70aba8f0cb6d11ae99715438f640cff11bbcf172eb0e8", size = 972009, upload-time = "2026-01-07T18:04:38.303Z" }, - { url = "https://files.pythonhosted.org/packages/1e/5e/9f3a8daf583d0adaaa033a3e3e58194d2282737dc164014ff33c7a081103/pymongo-4.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a19ea46a0fe71248965305a020bc076a163311aefbaa1d83e47d06fa30ac747", size = 971784, upload-time = "2026-01-07T18:04:39.669Z" }, - { url = "https://files.pythonhosted.org/packages/ad/f2/b6c24361fcde24946198573c0176406bfd5f7b8538335f3d939487055322/pymongo-4.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:311d4549d6bf1f8c61d025965aebb5ba29d1481dc6471693ab91610aaffbc0eb", size = 1947174, upload-time = "2026-01-07T18:04:41.368Z" }, - { url = "https://files.pythonhosted.org/packages/47/1a/8634192f98cf740b3d174e1018dd0350018607d5bd8ac35a666dc49c732b/pymongo-4.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46ffb728d92dd5b09fc034ed91acf5595657c7ca17d4cf3751322cd554153c17", size = 1991727, upload-time = "2026-01-07T18:04:42.965Z" }, - { url = "https://files.pythonhosted.org/packages/5a/2f/0c47ac84572b28e23028a23a3798a1f725e1c23b0cf1c1424678d16aff42/pymongo-4.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:acda193f440dd88c2023cb00aa8bd7b93a9df59978306d14d87a8b12fe426b05", size = 2082497, upload-time = "2026-01-07T18:04:44.652Z" }, - { url = "https://files.pythonhosted.org/packages/ba/57/9f46ef9c862b2f0cf5ce798f3541c201c574128d31ded407ba4b3918d7b6/pymongo-4.16.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d9fdb386cf958e6ef6ff537d6149be7edb76c3268cd6833e6c36aa447e4443f", size = 2064947, upload-time = "2026-01-07T18:04:46.228Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/5421c0998f38e32288100a07f6cb2f5f9f352522157c901910cb2927e211/pymongo-4.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91899dd7fb9a8c50f09c3c1cf0cb73bfbe2737f511f641f19b9650deb61c00ca", size = 1980478, upload-time = "2026-01-07T18:04:48.017Z" }, - { url = "https://files.pythonhosted.org/packages/92/93/bfc448d025e12313a937d6e1e0101b50cc9751636b4b170e600fe3203063/pymongo-4.16.0-cp313-cp313-win32.whl", hash = "sha256:2cd60cd1e05de7f01927f8e25ca26b3ea2c09de8723241e5d3bcfdc70eaff76b", size = 934672, upload-time = "2026-01-07T18:04:49.538Z" }, - { url = "https://files.pythonhosted.org/packages/96/10/12710a5e01218d50c3dd165fd72c5ed2699285f77348a3b1a119a191d826/pymongo-4.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ead8a0050c53eaa55935895d6919d393d0328ec24b2b9115bdbe881aa222673", size = 959237, upload-time = "2026-01-07T18:04:51.382Z" }, - { url = "https://files.pythonhosted.org/packages/0c/56/d288bcd1d05bc17ec69df1d0b1d67bc710c7c5dbef86033a5a4d2e2b08e6/pymongo-4.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:dbbc5b254c36c37d10abb50e899bc3939bbb7ab1e7c659614409af99bd3e7675", size = 940909, upload-time = "2026-01-07T18:04:52.904Z" }, - { url = "https://files.pythonhosted.org/packages/30/9e/4d343f8d0512002fce17915a89477b9f916bda1205729e042d8f23acf194/pymongo-4.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8a254d49a9ffe9d7f888e3c677eed3729b14ce85abb08cd74732cead6ccc3c66", size = 1026634, upload-time = "2026-01-07T18:04:54.359Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/341f88c5535df40c0450fda915f582757bb7d988cdfc92990a5e27c4c324/pymongo-4.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1bf44e13cf2d44d2ea2e928a8140d5d667304abe1a61c4d55b4906f389fbe64", size = 1026252, upload-time = "2026-01-07T18:04:56.642Z" }, - { url = "https://files.pythonhosted.org/packages/af/64/9471b22eb98f0a2ca0b8e09393de048502111b2b5b14ab1bd9e39708aab5/pymongo-4.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f1c5f1f818b669875d191323a48912d3fcd2e4906410e8297bb09ac50c4d5ccc", size = 2207399, upload-time = "2026-01-07T18:04:58.255Z" }, - { url = "https://files.pythonhosted.org/packages/87/ac/47c4d50b25a02f21764f140295a2efaa583ee7f17992a5e5fa542b3a690f/pymongo-4.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77cfd37a43a53b02b7bd930457c7994c924ad8bbe8dff91817904bcbf291b371", size = 2260595, upload-time = "2026-01-07T18:04:59.788Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1b/0ce1ce9dd036417646b2fe6f63b58127acff3cf96eeb630c34ec9cd675ff/pymongo-4.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:36ef2fee50eee669587d742fb456e349634b4fcf8926208766078b089054b24b", size = 2366958, upload-time = "2026-01-07T18:05:01.942Z" }, - { url = "https://files.pythonhosted.org/packages/3e/3c/a5a17c0d413aa9d6c17bc35c2b472e9e79cda8068ba8e93433b5f43028e9/pymongo-4.16.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55f8d5a6fe2fa0b823674db2293f92d74cd5f970bc0360f409a1fc21003862d3", size = 2346081, upload-time = "2026-01-07T18:05:03.576Z" }, - { url = "https://files.pythonhosted.org/packages/65/19/f815533d1a88fb8a3b6c6e895bb085ffdae68ccb1e6ed7102202a307f8e2/pymongo-4.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9caacac0dd105e2555521002e2d17afc08665187017b466b5753e84c016628e6", size = 2246053, upload-time = "2026-01-07T18:05:05.459Z" }, - { url = "https://files.pythonhosted.org/packages/c6/88/4be3ec78828dc64b212c123114bd6ae8db5b7676085a7b43cc75d0131bd2/pymongo-4.16.0-cp314-cp314-win32.whl", hash = "sha256:c789236366525c3ee3cd6e4e450a9ff629a7d1f4d88b8e18a0aea0615fd7ecf8", size = 989461, upload-time = "2026-01-07T18:05:07.018Z" }, - { url = "https://files.pythonhosted.org/packages/af/5a/ab8d5af76421b34db483c9c8ebc3a2199fb80ae63dc7e18f4cf1df46306a/pymongo-4.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b0714d7764efb29bf9d3c51c964aed7c4c7237b341f9346f15ceaf8321fdb35", size = 1017803, upload-time = "2026-01-07T18:05:08.499Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/98d68020728ac6423cf02d17cfd8226bf6cce5690b163d30d3f705e8297e/pymongo-4.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:12762e7cc0f8374a8cae3b9f9ed8dabb5d438c7b33329232dd9b7de783454033", size = 997184, upload-time = "2026-01-07T18:05:09.944Z" }, - { url = "https://files.pythonhosted.org/packages/50/00/dc3a271daf06401825b9c1f4f76f018182c7738281ea54b9762aea0560c1/pymongo-4.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1c01e8a7cd0ea66baf64a118005535ab5bf9f9eb63a1b50ac3935dccf9a54abe", size = 1083303, upload-time = "2026-01-07T18:05:11.702Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4b/b5375ee21d12eababe46215011ebc63801c0d2c5ffdf203849d0d79f9852/pymongo-4.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c4872299ebe315a79f7f922051061634a64fda95b6b17677ba57ef00b2ba2a4", size = 1083233, upload-time = "2026-01-07T18:05:13.182Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e3/52efa3ca900622c7dcb56c5e70f15c906816d98905c22d2ee1f84d9a7b60/pymongo-4.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78037d02389745e247fe5ab0bcad5d1ab30726eaac3ad79219c7d6bbb07eec53", size = 2527438, upload-time = "2026-01-07T18:05:14.981Z" }, - { url = "https://files.pythonhosted.org/packages/cb/96/43b1be151c734e7766c725444bcbfa1de6b60cc66bfb406203746839dd25/pymongo-4.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c126fb72be2518395cc0465d4bae03125119136462e1945aea19840e45d89cfc", size = 2600399, upload-time = "2026-01-07T18:05:16.794Z" }, - { url = "https://files.pythonhosted.org/packages/e7/62/fa64a5045dfe3a1cd9217232c848256e7bc0136cffb7da4735c5e0d30e40/pymongo-4.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3867dc225d9423c245a51eaac2cfcd53dde8e0a8d8090bb6aed6e31bd6c2d4f", size = 2720960, upload-time = "2026-01-07T18:05:18.498Z" }, - { url = "https://files.pythonhosted.org/packages/54/7b/01577eb97e605502821273a5bc16ce0fb0be5c978fe03acdbff471471202/pymongo-4.16.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f25001a955073b80510c0c3db0e043dbbc36904fd69e511c74e3d8640b8a5111", size = 2699344, upload-time = "2026-01-07T18:05:20.073Z" }, - { url = "https://files.pythonhosted.org/packages/55/68/6ef6372d516f703479c3b6cbbc45a5afd307173b1cbaccd724e23919bb1a/pymongo-4.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d9885aad05f82fd7ea0c9ca505d60939746b39263fa273d0125170da8f59098", size = 2577133, upload-time = "2026-01-07T18:05:22.052Z" }, - { url = "https://files.pythonhosted.org/packages/15/c7/b5337093bb01da852f945802328665f85f8109dbe91d81ea2afe5ff059b9/pymongo-4.16.0-cp314-cp314t-win32.whl", hash = "sha256:948152b30eddeae8355495f9943a3bf66b708295c0b9b6f467de1c620f215487", size = 1040560, upload-time = "2026-01-07T18:05:23.888Z" }, - { url = "https://files.pythonhosted.org/packages/96/8c/5b448cd1b103f3889d5713dda37304c81020ff88e38a826e8a75ddff4610/pymongo-4.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f6e42c1bc985d9beee884780ae6048790eb4cd565c46251932906bdb1630034a", size = 1075081, upload-time = "2026-01-07T18:05:26.874Z" }, - { url = "https://files.pythonhosted.org/packages/32/cd/ddc794cdc8500f6f28c119c624252fb6dfb19481c6d7ed150f13cf468a6d/pymongo-4.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6b2a20edb5452ac8daa395890eeb076c570790dfce6b7a44d788af74c2f8cf96", size = 1047725, upload-time = "2026-01-07T18:05:28.47Z" }, + { url = "https://files.pythonhosted.org/packages/6a/03/6dd7c53cbde98de469a3e6fb893af896dca644c476beb0f0c6342bcc368b/pymongo-4.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd4911c40a43a821dfd93038ac824b756b6e703e26e951718522d29f6eb166a8", size = 917619 }, + { url = "https://files.pythonhosted.org/packages/73/e1/328915f2734ea1f355dc9b0e98505ff670f5fab8be5e951d6ed70971c6aa/pymongo-4.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25a6b03a68f9907ea6ec8bc7cf4c58a1b51a18e23394f962a6402f8e46d41211", size = 917364 }, + { url = "https://files.pythonhosted.org/packages/41/fe/4769874dd9812a1bc2880a9785e61eba5340da966af888dd430392790ae0/pymongo-4.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:91ac0cb0fe2bf17616c2039dac88d7c9a5088f5cb5829b27c9d250e053664d31", size = 1686901 }, + { url = "https://files.pythonhosted.org/packages/fa/8d/15707b9669fdc517bbc552ac60da7124dafe7ac1552819b51e97ed4038b4/pymongo-4.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf0ec79e8ca7077f455d14d915d629385153b6a11abc0b93283ed73a8013e376", size = 1723034 }, + { url = "https://files.pythonhosted.org/packages/5b/af/3d5d16ff11d447d40c1472da1b366a31c7380d7ea2922a449c7f7f495567/pymongo-4.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d0082631a7510318befc2b4fdab140481eb4b9dd62d9245e042157085da2a70", size = 1797161 }, + { url = "https://files.pythonhosted.org/packages/fb/04/725ab8664eeec73ec125b5a873448d80f5d8cf2750aaaf804cbc538a50a5/pymongo-4.16.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85dc2f3444c346ea019a371e321ac868a4fab513b7a55fe368f0cc78de8177cc", size = 1780938 }, + { url = "https://files.pythonhosted.org/packages/22/50/dd7e9095e1ca35f93c3c844c92eb6eb0bc491caeb2c9bff3b32fe3c9b18f/pymongo-4.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbf3c14de75a20cc3c30bf0c6527157224a93dfb605838eabb1a2ee3be008d", size = 1714342 }, + { url = "https://files.pythonhosted.org/packages/03/c9/542776987d5c31ae8e93e92680ea2b6e5a2295f398b25756234cabf38a39/pymongo-4.16.0-cp312-cp312-win32.whl", hash = "sha256:60307bb91e0ab44e560fe3a211087748b2b5f3e31f403baf41f5b7b0a70bd104", size = 887868 }, + { url = "https://files.pythonhosted.org/packages/2e/d4/b4045a7ccc5680fb496d01edf749c7a9367cc8762fbdf7516cf807ef679b/pymongo-4.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f513b2c6c0d5c491f478422f6b5b5c27ac1af06a54c93ef8631806f7231bd92e", size = 907554 }, + { url = "https://files.pythonhosted.org/packages/60/4c/33f75713d50d5247f2258405142c0318ff32c6f8976171c4fcae87a9dbdf/pymongo-4.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:dfc320f08ea9a7ec5b2403dc4e8150636f0d6150f4b9792faaae539c88e7db3b", size = 892971 }, + { url = "https://files.pythonhosted.org/packages/47/84/148d8b5da8260f4679d6665196ae04ab14ffdf06f5fe670b0ab11942951f/pymongo-4.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d15f060bc6d0964a8bb70aba8f0cb6d11ae99715438f640cff11bbcf172eb0e8", size = 972009 }, + { url = "https://files.pythonhosted.org/packages/1e/5e/9f3a8daf583d0adaaa033a3e3e58194d2282737dc164014ff33c7a081103/pymongo-4.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a19ea46a0fe71248965305a020bc076a163311aefbaa1d83e47d06fa30ac747", size = 971784 }, + { url = "https://files.pythonhosted.org/packages/ad/f2/b6c24361fcde24946198573c0176406bfd5f7b8538335f3d939487055322/pymongo-4.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:311d4549d6bf1f8c61d025965aebb5ba29d1481dc6471693ab91610aaffbc0eb", size = 1947174 }, + { url = "https://files.pythonhosted.org/packages/47/1a/8634192f98cf740b3d174e1018dd0350018607d5bd8ac35a666dc49c732b/pymongo-4.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46ffb728d92dd5b09fc034ed91acf5595657c7ca17d4cf3751322cd554153c17", size = 1991727 }, + { url = "https://files.pythonhosted.org/packages/5a/2f/0c47ac84572b28e23028a23a3798a1f725e1c23b0cf1c1424678d16aff42/pymongo-4.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:acda193f440dd88c2023cb00aa8bd7b93a9df59978306d14d87a8b12fe426b05", size = 2082497 }, + { url = "https://files.pythonhosted.org/packages/ba/57/9f46ef9c862b2f0cf5ce798f3541c201c574128d31ded407ba4b3918d7b6/pymongo-4.16.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d9fdb386cf958e6ef6ff537d6149be7edb76c3268cd6833e6c36aa447e4443f", size = 2064947 }, + { url = "https://files.pythonhosted.org/packages/b8/56/5421c0998f38e32288100a07f6cb2f5f9f352522157c901910cb2927e211/pymongo-4.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91899dd7fb9a8c50f09c3c1cf0cb73bfbe2737f511f641f19b9650deb61c00ca", size = 1980478 }, + { url = "https://files.pythonhosted.org/packages/92/93/bfc448d025e12313a937d6e1e0101b50cc9751636b4b170e600fe3203063/pymongo-4.16.0-cp313-cp313-win32.whl", hash = "sha256:2cd60cd1e05de7f01927f8e25ca26b3ea2c09de8723241e5d3bcfdc70eaff76b", size = 934672 }, + { url = "https://files.pythonhosted.org/packages/96/10/12710a5e01218d50c3dd165fd72c5ed2699285f77348a3b1a119a191d826/pymongo-4.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ead8a0050c53eaa55935895d6919d393d0328ec24b2b9115bdbe881aa222673", size = 959237 }, + { url = "https://files.pythonhosted.org/packages/0c/56/d288bcd1d05bc17ec69df1d0b1d67bc710c7c5dbef86033a5a4d2e2b08e6/pymongo-4.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:dbbc5b254c36c37d10abb50e899bc3939bbb7ab1e7c659614409af99bd3e7675", size = 940909 }, + { url = "https://files.pythonhosted.org/packages/30/9e/4d343f8d0512002fce17915a89477b9f916bda1205729e042d8f23acf194/pymongo-4.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8a254d49a9ffe9d7f888e3c677eed3729b14ce85abb08cd74732cead6ccc3c66", size = 1026634 }, + { url = "https://files.pythonhosted.org/packages/c3/e3/341f88c5535df40c0450fda915f582757bb7d988cdfc92990a5e27c4c324/pymongo-4.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1bf44e13cf2d44d2ea2e928a8140d5d667304abe1a61c4d55b4906f389fbe64", size = 1026252 }, + { url = "https://files.pythonhosted.org/packages/af/64/9471b22eb98f0a2ca0b8e09393de048502111b2b5b14ab1bd9e39708aab5/pymongo-4.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f1c5f1f818b669875d191323a48912d3fcd2e4906410e8297bb09ac50c4d5ccc", size = 2207399 }, + { url = "https://files.pythonhosted.org/packages/87/ac/47c4d50b25a02f21764f140295a2efaa583ee7f17992a5e5fa542b3a690f/pymongo-4.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77cfd37a43a53b02b7bd930457c7994c924ad8bbe8dff91817904bcbf291b371", size = 2260595 }, + { url = "https://files.pythonhosted.org/packages/ee/1b/0ce1ce9dd036417646b2fe6f63b58127acff3cf96eeb630c34ec9cd675ff/pymongo-4.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:36ef2fee50eee669587d742fb456e349634b4fcf8926208766078b089054b24b", size = 2366958 }, + { url = "https://files.pythonhosted.org/packages/3e/3c/a5a17c0d413aa9d6c17bc35c2b472e9e79cda8068ba8e93433b5f43028e9/pymongo-4.16.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55f8d5a6fe2fa0b823674db2293f92d74cd5f970bc0360f409a1fc21003862d3", size = 2346081 }, + { url = "https://files.pythonhosted.org/packages/65/19/f815533d1a88fb8a3b6c6e895bb085ffdae68ccb1e6ed7102202a307f8e2/pymongo-4.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9caacac0dd105e2555521002e2d17afc08665187017b466b5753e84c016628e6", size = 2246053 }, + { url = "https://files.pythonhosted.org/packages/c6/88/4be3ec78828dc64b212c123114bd6ae8db5b7676085a7b43cc75d0131bd2/pymongo-4.16.0-cp314-cp314-win32.whl", hash = "sha256:c789236366525c3ee3cd6e4e450a9ff629a7d1f4d88b8e18a0aea0615fd7ecf8", size = 989461 }, + { url = "https://files.pythonhosted.org/packages/af/5a/ab8d5af76421b34db483c9c8ebc3a2199fb80ae63dc7e18f4cf1df46306a/pymongo-4.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b0714d7764efb29bf9d3c51c964aed7c4c7237b341f9346f15ceaf8321fdb35", size = 1017803 }, + { url = "https://files.pythonhosted.org/packages/f6/f4/98d68020728ac6423cf02d17cfd8226bf6cce5690b163d30d3f705e8297e/pymongo-4.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:12762e7cc0f8374a8cae3b9f9ed8dabb5d438c7b33329232dd9b7de783454033", size = 997184 }, + { url = "https://files.pythonhosted.org/packages/50/00/dc3a271daf06401825b9c1f4f76f018182c7738281ea54b9762aea0560c1/pymongo-4.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1c01e8a7cd0ea66baf64a118005535ab5bf9f9eb63a1b50ac3935dccf9a54abe", size = 1083303 }, + { url = "https://files.pythonhosted.org/packages/b8/4b/b5375ee21d12eababe46215011ebc63801c0d2c5ffdf203849d0d79f9852/pymongo-4.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c4872299ebe315a79f7f922051061634a64fda95b6b17677ba57ef00b2ba2a4", size = 1083233 }, + { url = "https://files.pythonhosted.org/packages/ee/e3/52efa3ca900622c7dcb56c5e70f15c906816d98905c22d2ee1f84d9a7b60/pymongo-4.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78037d02389745e247fe5ab0bcad5d1ab30726eaac3ad79219c7d6bbb07eec53", size = 2527438 }, + { url = "https://files.pythonhosted.org/packages/cb/96/43b1be151c734e7766c725444bcbfa1de6b60cc66bfb406203746839dd25/pymongo-4.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c126fb72be2518395cc0465d4bae03125119136462e1945aea19840e45d89cfc", size = 2600399 }, + { url = "https://files.pythonhosted.org/packages/e7/62/fa64a5045dfe3a1cd9217232c848256e7bc0136cffb7da4735c5e0d30e40/pymongo-4.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3867dc225d9423c245a51eaac2cfcd53dde8e0a8d8090bb6aed6e31bd6c2d4f", size = 2720960 }, + { url = "https://files.pythonhosted.org/packages/54/7b/01577eb97e605502821273a5bc16ce0fb0be5c978fe03acdbff471471202/pymongo-4.16.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f25001a955073b80510c0c3db0e043dbbc36904fd69e511c74e3d8640b8a5111", size = 2699344 }, + { url = "https://files.pythonhosted.org/packages/55/68/6ef6372d516f703479c3b6cbbc45a5afd307173b1cbaccd724e23919bb1a/pymongo-4.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d9885aad05f82fd7ea0c9ca505d60939746b39263fa273d0125170da8f59098", size = 2577133 }, + { url = "https://files.pythonhosted.org/packages/15/c7/b5337093bb01da852f945802328665f85f8109dbe91d81ea2afe5ff059b9/pymongo-4.16.0-cp314-cp314t-win32.whl", hash = "sha256:948152b30eddeae8355495f9943a3bf66b708295c0b9b6f467de1c620f215487", size = 1040560 }, + { url = "https://files.pythonhosted.org/packages/96/8c/5b448cd1b103f3889d5713dda37304c81020ff88e38a826e8a75ddff4610/pymongo-4.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f6e42c1bc985d9beee884780ae6048790eb4cd565c46251932906bdb1630034a", size = 1075081 }, + { url = "https://files.pythonhosted.org/packages/32/cd/ddc794cdc8500f6f28c119c624252fb6dfb19481c6d7ed150f13cf468a6d/pymongo-4.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6b2a20edb5452ac8daa395890eeb076c570790dfce6b7a44d788af74c2f8cf96", size = 1047725 }, ] [[package]] name = "pyobjc-core" version = "12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" }, - { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" }, - { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" }, - { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" }, + { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335 }, + { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370 }, + { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586 }, + { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164 }, + { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204 }, ] [[package]] @@ -6738,13 +6721,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, - { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" }, - { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590 }, + { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689 }, + { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843 }, + { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932 }, + { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970 }, ] [[package]] @@ -6755,13 +6738,13 @@ dependencies = [ { name = "pyobjc-core", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-cocoa", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3f/3749964aa3583f8c30d9996f0d15541120b78d307bb3070f5e47154ef38d/pyobjc_framework_coreml-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:48fa3bb4a03fa23e0e36c93936dca2969598e4102f4b441e1663f535fc99cd31", size = 11371, upload-time = "2025-11-14T09:45:54.105Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c8/cf20ea91ae33f05f3b92dec648c6f44a65f86d1a64c1d6375c95b85ccb7c/pyobjc_framework_coreml-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:71de5b37e6a017e3ed16645c5d6533138f24708da5b56c35c818ae49d0253ee1", size = 11600, upload-time = "2025-11-14T09:45:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5c/510ae8e3663238d32e653ed6a09ac65611dd045a7241f12633c1ab48bb9b/pyobjc_framework_coreml-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a04a96e512ecf6999aa9e1f60ad5635cb9d1cd839be470341d8d1541797baef6", size = 11418, upload-time = "2025-11-14T09:45:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1a/b7367819381b07c440fa5797d2b0487e31f09aa72079a693ceab6875fa0a/pyobjc_framework_coreml-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7762b3dd2de01565b7cf3049ce1e4c27341ba179d97016b0b7607448e1c39865", size = 11593, upload-time = "2025-11-14T09:45:59.623Z" }, + { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356 }, + { url = "https://files.pythonhosted.org/packages/ae/3f/3749964aa3583f8c30d9996f0d15541120b78d307bb3070f5e47154ef38d/pyobjc_framework_coreml-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:48fa3bb4a03fa23e0e36c93936dca2969598e4102f4b441e1663f535fc99cd31", size = 11371 }, + { url = "https://files.pythonhosted.org/packages/9c/c8/cf20ea91ae33f05f3b92dec648c6f44a65f86d1a64c1d6375c95b85ccb7c/pyobjc_framework_coreml-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:71de5b37e6a017e3ed16645c5d6533138f24708da5b56c35c818ae49d0253ee1", size = 11600 }, + { url = "https://files.pythonhosted.org/packages/bc/5c/510ae8e3663238d32e653ed6a09ac65611dd045a7241f12633c1ab48bb9b/pyobjc_framework_coreml-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a04a96e512ecf6999aa9e1f60ad5635cb9d1cd839be470341d8d1541797baef6", size = 11418 }, + { url = "https://files.pythonhosted.org/packages/d3/1a/b7367819381b07c440fa5797d2b0487e31f09aa72079a693ceab6875fa0a/pyobjc_framework_coreml-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7762b3dd2de01565b7cf3049ce1e4c27341ba179d97016b0b7607448e1c39865", size = 11593 }, ] [[package]] @@ -6772,13 +6755,13 @@ dependencies = [ { name = "pyobjc-core", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-cocoa", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" }, - { url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" }, - { url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798 }, + { url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206 }, + { url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317 }, + { url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558 }, + { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580 }, ] [[package]] @@ -6791,22 +6774,22 @@ dependencies = [ { name = "pyobjc-framework-coreml", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-quartz", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e4/e87361a31b82b22f8c0a59652d6e17625870dd002e8da75cb2343a84f2f9/pyobjc_framework_vision-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7273e2508db4c2e88523b4b7ff38ac54808756e7ba01d78e6c08ea68f32577d2", size = 16640, upload-time = "2025-11-14T10:06:46.653Z" }, - { url = "https://files.pythonhosted.org/packages/b1/dd/def55d8a80b0817f486f2712fc6243482c3264d373dc5ff75037b3aeb7ea/pyobjc_framework_vision-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:04296f0848cc8cdead66c76df6063720885cbdf24fdfd1900749a6e2297313db", size = 16782, upload-time = "2025-11-14T10:06:48.816Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a4/ee1ef14d6e1df6617e64dbaaa0ecf8ecb9e0af1425613fa633f6a94049c1/pyobjc_framework_vision-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:631add775ed1dafb221a6116137cdcd78432addc16200ca434571c2a039c0e03", size = 16614, upload-time = "2025-11-14T10:06:50.852Z" }, - { url = "https://files.pythonhosted.org/packages/af/53/187743d9244becd4499a77f8ee699ae286e2f6ade7c0c7ad2975ae60f187/pyobjc_framework_vision-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fe41a1a70cc91068aee7b5293fa09dc66d1c666a8da79fdf948900988b439df6", size = 16771, upload-time = "2025-11-14T10:06:53.04Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625 }, + { url = "https://files.pythonhosted.org/packages/f5/e4/e87361a31b82b22f8c0a59652d6e17625870dd002e8da75cb2343a84f2f9/pyobjc_framework_vision-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7273e2508db4c2e88523b4b7ff38ac54808756e7ba01d78e6c08ea68f32577d2", size = 16640 }, + { url = "https://files.pythonhosted.org/packages/b1/dd/def55d8a80b0817f486f2712fc6243482c3264d373dc5ff75037b3aeb7ea/pyobjc_framework_vision-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:04296f0848cc8cdead66c76df6063720885cbdf24fdfd1900749a6e2297313db", size = 16782 }, + { url = "https://files.pythonhosted.org/packages/a7/a4/ee1ef14d6e1df6617e64dbaaa0ecf8ecb9e0af1425613fa633f6a94049c1/pyobjc_framework_vision-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:631add775ed1dafb221a6116137cdcd78432addc16200ca434571c2a039c0e03", size = 16614 }, + { url = "https://files.pythonhosted.org/packages/af/53/187743d9244becd4499a77f8ee699ae286e2f6ade7c0c7ad2975ae60f187/pyobjc_framework_vision-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fe41a1a70cc91068aee7b5293fa09dc66d1c666a8da79fdf948900988b439df6", size = 16771 }, ] [[package]] name = "pypandoc" version = "1.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/d6/410615fc433e5d1eacc00db2044ae2a9c82302df0d35366fe2bd15de024d/pypandoc-1.17.tar.gz", hash = "sha256:51179abfd6e582a25ed03477541b48836b5bba5a4c3b282a547630793934d799", size = 69071, upload-time = "2026-03-14T22:39:07.21Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/d6/410615fc433e5d1eacc00db2044ae2a9c82302df0d35366fe2bd15de024d/pypandoc-1.17.tar.gz", hash = "sha256:51179abfd6e582a25ed03477541b48836b5bba5a4c3b282a547630793934d799", size = 69071 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/86/e2ffa604eacfbec3f430b1d850e7e04c4101eca1a5828f9ae54bf51dfba4/pypandoc-1.17-py3-none-any.whl", hash = "sha256:01fdbffa61edb9f8e82e8faad6954efcb7b6f8f0634aead4d89e322a00225a67", size = 23554, upload-time = "2026-03-14T22:38:46.007Z" }, + { url = "https://files.pythonhosted.org/packages/0c/86/e2ffa604eacfbec3f430b1d850e7e04c4101eca1a5828f9ae54bf51dfba4/pypandoc-1.17-py3-none-any.whl", hash = "sha256:01fdbffa61edb9f8e82e8faad6954efcb7b6f8f0634aead4d89e322a00225a67", size = 23554 }, ] [[package]] @@ -6814,78 +6797,78 @@ name = "pypandoc-binary" version = "1.17" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/85/681a54111f0948821a5cf87ce30a88bb0a3f6848af5112c912abac4a2b77/pypandoc_binary-1.17-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:734726dc618ef276343e272e1a6b4567e59c2ef9ef41d5533042deac3b0531f1", size = 25553945, upload-time = "2026-03-14T22:38:47.91Z" }, - { url = "https://files.pythonhosted.org/packages/15/58/8fd107c68522957868c1e785fbea7595608df118e440e424d189668294df/pypandoc_binary-1.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fcfd28f347ed998dda28823fc6bc24f9310e7fdf3ddceaf925bf0563a100ab5b", size = 25553944, upload-time = "2026-03-14T22:38:50.74Z" }, - { url = "https://files.pythonhosted.org/packages/f4/27/ac1078239aae14b94c51975b7f46ad8e099e47d7ae26c175a5486b1c0099/pypandoc_binary-1.17-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b620b21c9374e3e48aabd518492bf0776b148442ee28816f6aaf52da3d4387", size = 34460960, upload-time = "2026-03-14T22:38:53.391Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7f/1e5612b52900ebe590862dabeadf546f739b27527dcd8bfd632f8adac1be/pypandoc_binary-1.17-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ada156cb980cd54fd6534231788e668c00dbb591cbd24f0be0bd86812eb8788", size = 36867598, upload-time = "2026-03-14T22:38:56.351Z" }, - { url = "https://files.pythonhosted.org/packages/3b/31/a5a867159c4080e5d368f4a53540a727501a2f31affc297dc8e0fced96a7/pypandoc_binary-1.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2f439dcd211183bb3460253ca4511101df6e1acf4a01f45f5617e1fa2ad24279", size = 36867584, upload-time = "2026-03-14T22:38:59.087Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2d/6a51cd4e54bdf132c19416801077c34bd40ba182e85d843360d36ae03a2d/pypandoc_binary-1.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6e6d3e4cfafbe23189a08db3d41f8def260bacd6e7e382bceadab7ba1f17da6", size = 34460949, upload-time = "2026-03-14T22:39:01.71Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b9/f47b77ba75ed5d47ec85fcc2ecfbf7f78e3a73347f3a09836634d930de98/pypandoc_binary-1.17-py3-none-win_amd64.whl", hash = "sha256:76fae066cd2d7e78fb97f0ec8e9e36f437b07187b689b0b415ca18216f8f898a", size = 40891661, upload-time = "2026-03-14T22:39:04.782Z" }, + { url = "https://files.pythonhosted.org/packages/80/85/681a54111f0948821a5cf87ce30a88bb0a3f6848af5112c912abac4a2b77/pypandoc_binary-1.17-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:734726dc618ef276343e272e1a6b4567e59c2ef9ef41d5533042deac3b0531f1", size = 25553945 }, + { url = "https://files.pythonhosted.org/packages/15/58/8fd107c68522957868c1e785fbea7595608df118e440e424d189668294df/pypandoc_binary-1.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fcfd28f347ed998dda28823fc6bc24f9310e7fdf3ddceaf925bf0563a100ab5b", size = 25553944 }, + { url = "https://files.pythonhosted.org/packages/f4/27/ac1078239aae14b94c51975b7f46ad8e099e47d7ae26c175a5486b1c0099/pypandoc_binary-1.17-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b620b21c9374e3e48aabd518492bf0776b148442ee28816f6aaf52da3d4387", size = 34460960 }, + { url = "https://files.pythonhosted.org/packages/8d/7f/1e5612b52900ebe590862dabeadf546f739b27527dcd8bfd632f8adac1be/pypandoc_binary-1.17-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ada156cb980cd54fd6534231788e668c00dbb591cbd24f0be0bd86812eb8788", size = 36867598 }, + { url = "https://files.pythonhosted.org/packages/3b/31/a5a867159c4080e5d368f4a53540a727501a2f31affc297dc8e0fced96a7/pypandoc_binary-1.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2f439dcd211183bb3460253ca4511101df6e1acf4a01f45f5617e1fa2ad24279", size = 36867584 }, + { url = "https://files.pythonhosted.org/packages/0d/2d/6a51cd4e54bdf132c19416801077c34bd40ba182e85d843360d36ae03a2d/pypandoc_binary-1.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6e6d3e4cfafbe23189a08db3d41f8def260bacd6e7e382bceadab7ba1f17da6", size = 34460949 }, + { url = "https://files.pythonhosted.org/packages/c6/b9/f47b77ba75ed5d47ec85fcc2ecfbf7f78e3a73347f3a09836634d930de98/pypandoc_binary-1.17-py3-none-win_amd64.whl", hash = "sha256:76fae066cd2d7e78fb97f0ec8e9e36f437b07187b689b0b415ca18216f8f898a", size = 40891661 }, ] [[package]] name = "pyparsing" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781 }, ] [[package]] name = "pypdf" version = "6.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/83/691bdb309306232362503083cb15777491045dd54f45393a317dc7d8082f/pypdf-6.9.2.tar.gz", hash = "sha256:7f850faf2b0d4ab936582c05da32c52214c2b089d61a316627b5bfb5b0dab46c", size = 5311837, upload-time = "2026-03-23T14:53:27.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/83/691bdb309306232362503083cb15777491045dd54f45393a317dc7d8082f/pypdf-6.9.2.tar.gz", hash = "sha256:7f850faf2b0d4ab936582c05da32c52214c2b089d61a316627b5bfb5b0dab46c", size = 5311837 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/7e/c85f41243086a8fe5d1baeba527cb26a1918158a565932b41e0f7c0b32e9/pypdf-6.9.2-py3-none-any.whl", hash = "sha256:662cf29bcb419a36a1365232449624ab40b7c2d0cfc28e54f42eeecd1fd7e844", size = 333744, upload-time = "2026-03-23T14:53:26.573Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7e/c85f41243086a8fe5d1baeba527cb26a1918158a565932b41e0f7c0b32e9/pypdf-6.9.2-py3-none-any.whl", hash = "sha256:662cf29bcb419a36a1365232449624ab40b7c2d0cfc28e54f42eeecd1fd7e844", size = 333744 }, ] [[package]] name = "pypdfium2" version = "5.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/01/be763b9081c7eb823196e7d13d9c145bf75ac43f3c1466de81c21c24b381/pypdfium2-5.6.0.tar.gz", hash = "sha256:bcb9368acfe3547054698abbdae68ba0cbd2d3bda8e8ee437e061deef061976d", size = 270714, upload-time = "2026-03-08T01:05:06.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/01/be763b9081c7eb823196e7d13d9c145bf75ac43f3c1466de81c21c24b381/pypdfium2-5.6.0.tar.gz", hash = "sha256:bcb9368acfe3547054698abbdae68ba0cbd2d3bda8e8ee437e061deef061976d", size = 270714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/b1/129ed0177521a93a892f8a6a215dd3260093e30e77ef7035004bb8af7b6c/pypdfium2-5.6.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:fb7858c9707708555b4a719b5548a6e7f5d26bc82aef55ae4eb085d7a2190b11", size = 3346059, upload-time = "2026-03-08T01:04:21.37Z" }, - { url = "https://files.pythonhosted.org/packages/86/34/cbdece6886012180a7f2c7b2c360c415cf5e1f83f1973d2c9201dae3506a/pypdfium2-5.6.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:6a7e1f4597317786f994bfb947eef480e53933f804a990193ab89eef8243f805", size = 2804418, upload-time = "2026-03-08T01:04:23.384Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f6/9f9e190fe0e5a6b86b82f83bd8b5d3490348766062381140ca5cad8e00b1/pypdfium2-5.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e468c38997573f0e86f03273c2c1fbdea999de52ba43fee96acaa2f6b2ad35f7", size = 3412541, upload-time = "2026-03-08T01:04:25.45Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8d/e57492cb2228ba56ed57de1ff044c8ac114b46905f8b1445c33299ba0488/pypdfium2-5.6.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ad3abddc5805424f962e383253ccad6a0d1d2ebd86afa9a9e1b9ca659773cd0d", size = 3592320, upload-time = "2026-03-08T01:04:27.509Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8a/8ab82e33e9c551494cbe1526ea250ca8cc4e9e98d6a4fc6b6f8d959aa1d1/pypdfium2-5.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b5eb9eae5c45076395454522ca26add72ba8bd1fe473e1e4721aa58521470c", size = 3596450, upload-time = "2026-03-08T01:04:29.183Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b5/602a792282312ccb158cc63849528079d94b0a11efdc61f2a359edfb41e9/pypdfium2-5.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:258624da8ef45cdc426e11b33e9d83f9fb723c1c201c6e0f4ab5a85966c6b876", size = 3325442, upload-time = "2026-03-08T01:04:30.886Z" }, - { url = "https://files.pythonhosted.org/packages/81/1f/9e48ec05ed8d19d736c2d1f23c1bd0f20673f02ef846a2576c69e237f15d/pypdfium2-5.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9367451c8a00931d6612db0822525a18c06f649d562cd323a719e46ac19c9bb", size = 3727434, upload-time = "2026-03-08T01:04:33.619Z" }, - { url = "https://files.pythonhosted.org/packages/33/90/0efd020928b4edbd65f4f3c2af0c84e20b43a3ada8fa6d04f999a97afe7a/pypdfium2-5.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a757869f891eac1cc1372e38a4aa01adac8abc8fe2a8a4e2ebf50595e3bf5937", size = 4139029, upload-time = "2026-03-08T01:04:36.08Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/a640b288a48dab1752281dd9b72c0679fccea107874e80a65a606b00efa9/pypdfium2-5.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:515be355222cc57ae9e62cd5c7c350b8e0c863efc539f80c7d75e2811ba45cb6", size = 3646387, upload-time = "2026-03-08T01:04:38.151Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/a344c19c01021eeb5d830c102e4fc9b1602f19c04aa7d11abbe2d188fd8e/pypdfium2-5.6.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c4753c7caf7d004211d7f57a21f10d127f5e0e5510a14d24bc073e7220a3ea", size = 3097212, upload-time = "2026-03-08T01:04:40.776Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/e48e13789ace22aeb9b7510904a1b1493ec588196e11bbacc122da330b3d/pypdfium2-5.6.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c49729090281fdd85775fb8912c10bd19e99178efaa98f145ab06e7ce68554d2", size = 2965026, upload-time = "2026-03-08T01:04:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/cb/06/3100e44d4935f73af8f5d633d3bd40f0d36d606027085a0ef1f0566a6320/pypdfium2-5.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a4a1749a8d4afd62924a8d95cfa4f2e26fc32957ce34ac3b674be6f127ed252e", size = 4131431, upload-time = "2026-03-08T01:04:44.982Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/d8df63569ce9a66c8496057782eb8af78e0d28667922d62ec958434e3d4b/pypdfium2-5.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:36469ebd0fdffb7130ce45ed9c44f8232d91571c89eb851bd1633c64b6f6114f", size = 3747469, upload-time = "2026-03-08T01:04:46.702Z" }, - { url = "https://files.pythonhosted.org/packages/a6/47/fd2c6a67a49fade1acd719fbd11f7c375e7219912923ef2de0ea0ac1544e/pypdfium2-5.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da900df09be3cf546b637a127a7b6428fb22d705951d731269e25fd3adef457", size = 4337578, upload-time = "2026-03-08T01:04:49.007Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f5/836c83e54b01e09478c4d6bf4912651d6053c932250fcee953f5c72d8e4a/pypdfium2-5.6.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:45fccd5622233c5ec91a885770ae7dd4004d4320ac05a4ad8fa03a66dea40244", size = 4376104, upload-time = "2026-03-08T01:04:51.04Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7f/b940b6a1664daf8f9bad87c6c99b84effa3611615b8708d10392dc33036c/pypdfium2-5.6.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:282dc030e767cd61bd0299f9d581052b91188e2b87561489057a8e7963e7e0cb", size = 3929824, upload-time = "2026-03-08T01:04:53.544Z" }, - { url = "https://files.pythonhosted.org/packages/88/79/00267d92a6a58c229e364d474f5698efe446e0c7f4f152f58d0138715e99/pypdfium2-5.6.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:a1c1dfe950382c76a7bba1ba160ec5e40df8dd26b04a1124ae268fda55bc4cbe", size = 4270201, upload-time = "2026-03-08T01:04:55.81Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ab/b127f38aba41746bdf9ace15ba08411d7ef6ecba1326d529ba414eb1ed50/pypdfium2-5.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43b0341ca6feb6c92e4b7a9eb4813e5466f5f5e8b6baeb14df0a94d5f312c00b", size = 4180793, upload-time = "2026-03-08T01:04:57.961Z" }, - { url = "https://files.pythonhosted.org/packages/0e/8c/a01c8e4302448b614d25a85c08298b0d3e9dfbdac5bd1b2f32c9b02e83d9/pypdfium2-5.6.0-py3-none-win32.whl", hash = "sha256:9dfcd4ff49a2b9260d00e38539ab28190d59e785e83030b30ffaf7a29c42155d", size = 3596753, upload-time = "2026-03-08T01:05:00.566Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5f/2d871adf46761bb002a62686545da6348afe838d19af03df65d1ece786a2/pypdfium2-5.6.0-py3-none-win_amd64.whl", hash = "sha256:c6bc8dd63d0568f4b592f3e03de756afafc0e44aa1fe8878cc4aba1b11ae7374", size = 3716526, upload-time = "2026-03-08T01:05:02.433Z" }, - { url = "https://files.pythonhosted.org/packages/3a/80/0d9b162098597fbe3ac2b269b1682c0c3e8db9ba87679603fdd9b19afaa6/pypdfium2-5.6.0-py3-none-win_arm64.whl", hash = "sha256:5538417b199bdcb3207370c88df61f2ba3dac7a3253f82e1aa2708e6376b6f90", size = 3515049, upload-time = "2026-03-08T01:05:04.587Z" }, + { url = "https://files.pythonhosted.org/packages/9d/b1/129ed0177521a93a892f8a6a215dd3260093e30e77ef7035004bb8af7b6c/pypdfium2-5.6.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:fb7858c9707708555b4a719b5548a6e7f5d26bc82aef55ae4eb085d7a2190b11", size = 3346059 }, + { url = "https://files.pythonhosted.org/packages/86/34/cbdece6886012180a7f2c7b2c360c415cf5e1f83f1973d2c9201dae3506a/pypdfium2-5.6.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:6a7e1f4597317786f994bfb947eef480e53933f804a990193ab89eef8243f805", size = 2804418 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/9f9e190fe0e5a6b86b82f83bd8b5d3490348766062381140ca5cad8e00b1/pypdfium2-5.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e468c38997573f0e86f03273c2c1fbdea999de52ba43fee96acaa2f6b2ad35f7", size = 3412541 }, + { url = "https://files.pythonhosted.org/packages/ee/8d/e57492cb2228ba56ed57de1ff044c8ac114b46905f8b1445c33299ba0488/pypdfium2-5.6.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ad3abddc5805424f962e383253ccad6a0d1d2ebd86afa9a9e1b9ca659773cd0d", size = 3592320 }, + { url = "https://files.pythonhosted.org/packages/f9/8a/8ab82e33e9c551494cbe1526ea250ca8cc4e9e98d6a4fc6b6f8d959aa1d1/pypdfium2-5.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b5eb9eae5c45076395454522ca26add72ba8bd1fe473e1e4721aa58521470c", size = 3596450 }, + { url = "https://files.pythonhosted.org/packages/f5/b5/602a792282312ccb158cc63849528079d94b0a11efdc61f2a359edfb41e9/pypdfium2-5.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:258624da8ef45cdc426e11b33e9d83f9fb723c1c201c6e0f4ab5a85966c6b876", size = 3325442 }, + { url = "https://files.pythonhosted.org/packages/81/1f/9e48ec05ed8d19d736c2d1f23c1bd0f20673f02ef846a2576c69e237f15d/pypdfium2-5.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9367451c8a00931d6612db0822525a18c06f649d562cd323a719e46ac19c9bb", size = 3727434 }, + { url = "https://files.pythonhosted.org/packages/33/90/0efd020928b4edbd65f4f3c2af0c84e20b43a3ada8fa6d04f999a97afe7a/pypdfium2-5.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a757869f891eac1cc1372e38a4aa01adac8abc8fe2a8a4e2ebf50595e3bf5937", size = 4139029 }, + { url = "https://files.pythonhosted.org/packages/ff/49/a640b288a48dab1752281dd9b72c0679fccea107874e80a65a606b00efa9/pypdfium2-5.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:515be355222cc57ae9e62cd5c7c350b8e0c863efc539f80c7d75e2811ba45cb6", size = 3646387 }, + { url = "https://files.pythonhosted.org/packages/b0/3b/a344c19c01021eeb5d830c102e4fc9b1602f19c04aa7d11abbe2d188fd8e/pypdfium2-5.6.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c4753c7caf7d004211d7f57a21f10d127f5e0e5510a14d24bc073e7220a3ea", size = 3097212 }, + { url = "https://files.pythonhosted.org/packages/50/96/e48e13789ace22aeb9b7510904a1b1493ec588196e11bbacc122da330b3d/pypdfium2-5.6.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c49729090281fdd85775fb8912c10bd19e99178efaa98f145ab06e7ce68554d2", size = 2965026 }, + { url = "https://files.pythonhosted.org/packages/cb/06/3100e44d4935f73af8f5d633d3bd40f0d36d606027085a0ef1f0566a6320/pypdfium2-5.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a4a1749a8d4afd62924a8d95cfa4f2e26fc32957ce34ac3b674be6f127ed252e", size = 4131431 }, + { url = "https://files.pythonhosted.org/packages/64/ef/d8df63569ce9a66c8496057782eb8af78e0d28667922d62ec958434e3d4b/pypdfium2-5.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:36469ebd0fdffb7130ce45ed9c44f8232d91571c89eb851bd1633c64b6f6114f", size = 3747469 }, + { url = "https://files.pythonhosted.org/packages/a6/47/fd2c6a67a49fade1acd719fbd11f7c375e7219912923ef2de0ea0ac1544e/pypdfium2-5.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da900df09be3cf546b637a127a7b6428fb22d705951d731269e25fd3adef457", size = 4337578 }, + { url = "https://files.pythonhosted.org/packages/6b/f5/836c83e54b01e09478c4d6bf4912651d6053c932250fcee953f5c72d8e4a/pypdfium2-5.6.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:45fccd5622233c5ec91a885770ae7dd4004d4320ac05a4ad8fa03a66dea40244", size = 4376104 }, + { url = "https://files.pythonhosted.org/packages/6e/7f/b940b6a1664daf8f9bad87c6c99b84effa3611615b8708d10392dc33036c/pypdfium2-5.6.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:282dc030e767cd61bd0299f9d581052b91188e2b87561489057a8e7963e7e0cb", size = 3929824 }, + { url = "https://files.pythonhosted.org/packages/88/79/00267d92a6a58c229e364d474f5698efe446e0c7f4f152f58d0138715e99/pypdfium2-5.6.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:a1c1dfe950382c76a7bba1ba160ec5e40df8dd26b04a1124ae268fda55bc4cbe", size = 4270201 }, + { url = "https://files.pythonhosted.org/packages/e1/ab/b127f38aba41746bdf9ace15ba08411d7ef6ecba1326d529ba414eb1ed50/pypdfium2-5.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43b0341ca6feb6c92e4b7a9eb4813e5466f5f5e8b6baeb14df0a94d5f312c00b", size = 4180793 }, + { url = "https://files.pythonhosted.org/packages/0e/8c/a01c8e4302448b614d25a85c08298b0d3e9dfbdac5bd1b2f32c9b02e83d9/pypdfium2-5.6.0-py3-none-win32.whl", hash = "sha256:9dfcd4ff49a2b9260d00e38539ab28190d59e785e83030b30ffaf7a29c42155d", size = 3596753 }, + { url = "https://files.pythonhosted.org/packages/9b/5f/2d871adf46761bb002a62686545da6348afe838d19af03df65d1ece786a2/pypdfium2-5.6.0-py3-none-win_amd64.whl", hash = "sha256:c6bc8dd63d0568f4b592f3e03de756afafc0e44aa1fe8878cc4aba1b11ae7374", size = 3716526 }, + { url = "https://files.pythonhosted.org/packages/3a/80/0d9b162098597fbe3ac2b269b1682c0c3e8db9ba87679603fdd9b19afaa6/pypdfium2-5.6.0-py3-none-win_arm64.whl", hash = "sha256:5538417b199bdcb3207370c88df61f2ba3dac7a3253f82e1aa2708e6376b6f90", size = 3515049 }, ] [[package]] name = "pypika" version = "0.51.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe", size = 80919, upload-time = "2026-02-04T11:27:48.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe", size = 80919 } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585, upload-time = "2026-02-04T11:27:46.251Z" }, + { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585 }, ] [[package]] name = "pyproject-hooks" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, ] [[package]] @@ -6896,7 +6879,7 @@ dependencies = [ { name = "requests" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/a0/d0638470df605ce266991fb04f74c69ab1bed3b90ac3838e9c3c8b69b66a/Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988", size = 9071, upload-time = "2022-10-10T13:41:09.936Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/a0/d0638470df605ce266991fb04f74c69ab1bed3b90ac3838e9c3c8b69b66a/Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988", size = 9071 } [[package]] name = "pytest" @@ -6909,9 +6892,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, ] [[package]] @@ -6922,9 +6905,9 @@ dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075 }, ] [[package]] @@ -6934,9 +6917,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095 }, ] [[package]] @@ -6946,9 +6929,9 @@ 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" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 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" }, + { 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 }, ] [[package]] @@ -6959,18 +6942,18 @@ dependencies = [ { name = "lxml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987 }, ] [[package]] name = "python-dotenv" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101 }, ] [[package]] @@ -6981,36 +6964,36 @@ dependencies = [ { name = "pyee" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/4d/7ecffb341d646e016be76e36f5a42cb32f409c9ca21a57b68f067fad3fc7/python_ffmpeg-2.0.12.tar.gz", hash = "sha256:19ac80af5a064a2f53c245af1a909b2d7648ea045500d96d3bcd507b88d43dc7", size = 14126292, upload-time = "2024-04-15T10:15:31.878Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/4d/7ecffb341d646e016be76e36f5a42cb32f409c9ca21a57b68f067fad3fc7/python_ffmpeg-2.0.12.tar.gz", hash = "sha256:19ac80af5a064a2f53c245af1a909b2d7648ea045500d96d3bcd507b88d43dc7", size = 14126292 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/6d/02e817aec661defe148cb9eb0c4eca2444846305f625c2243fb9f92a9045/python_ffmpeg-2.0.12-py3-none-any.whl", hash = "sha256:d86697da8dfb39335183e336d31baf42fb217468adf5ac97fd743898240faae3", size = 14411, upload-time = "2024-04-15T10:15:28.966Z" }, + { url = "https://files.pythonhosted.org/packages/7f/6d/02e817aec661defe148cb9eb0c4eca2444846305f625c2243fb9f92a9045/python_ffmpeg-2.0.12-py3-none-any.whl", hash = "sha256:d86697da8dfb39335183e336d31baf42fb217468adf5ac97fd743898240faae3", size = 14411 }, ] [[package]] name = "python-iso639" version = "2026.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/da/701fc47ea3b0579a8ae489d50d5b54f2ef3aeb7768afd31db1d1cfe9f24e/python_iso639-2026.1.31.tar.gz", hash = "sha256:55a1612c15e5fbd3a1fa269a309cbf1e7c13019356e3d6f75bb435ed44c45ddb", size = 174144, upload-time = "2026-01-31T15:04:48.105Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/da/701fc47ea3b0579a8ae489d50d5b54f2ef3aeb7768afd31db1d1cfe9f24e/python_iso639-2026.1.31.tar.gz", hash = "sha256:55a1612c15e5fbd3a1fa269a309cbf1e7c13019356e3d6f75bb435ed44c45ddb", size = 174144 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/3a/03ee682b04099e6b02b591955851b0347deb2e3691ae850112000c54ba12/python_iso639-2026.1.31-py3-none-any.whl", hash = "sha256:b2c48fa1300af1299dff4f1e1995ad1059996ed9f22270ea2d6d6bdc5fb03d4c", size = 167757, upload-time = "2026-01-31T15:04:46.458Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3a/03ee682b04099e6b02b591955851b0347deb2e3691ae850112000c54ba12/python_iso639-2026.1.31-py3-none-any.whl", hash = "sha256:b2c48fa1300af1299dff4f1e1995ad1059996ed9f22270ea2d6d6bdc5fb03d4c", size = 167757 }, ] [[package]] name = "python-magic" version = "0.4.27" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, ] [[package]] name = "python-multipart" version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579 }, ] [[package]] @@ -7022,9 +7005,9 @@ dependencies = [ { name = "olefile" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713, upload-time = "2025-02-03T17:13:47.415Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713 } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455, upload-time = "2025-02-03T17:13:46.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455 }, ] [[package]] @@ -7037,18 +7020,18 @@ dependencies = [ { name = "typing-extensions" }, { name = "xlsxwriter" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788 }, ] [[package]] name = "pytz" version = "2026.1.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489 }, ] [[package]] @@ -7056,70 +7039,70 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { 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/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040 }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102 }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700 }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700 }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318 }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714 }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800 }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540 }, ] [[package]] name = "pywin32-ctypes" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, ] [[package]] @@ -7135,72 +7118,72 @@ dependencies = [ { name = "pydantic" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/dd/f8a8261b83946af3cd65943c93c4f83e044f01184e8525404989d22a81a5/qdrant_client-1.17.1.tar.gz", hash = "sha256:22f990bbd63485ed97ba551a4c498181fcb723f71dcab5d6e4e43fe1050a2bc0", size = 344979, upload-time = "2026-03-13T17:13:44.678Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/dd/f8a8261b83946af3cd65943c93c4f83e044f01184e8525404989d22a81a5/qdrant_client-1.17.1.tar.gz", hash = "sha256:22f990bbd63485ed97ba551a4c498181fcb723f71dcab5d6e4e43fe1050a2bc0", size = 344979 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947, upload-time = "2026-03-13T17:13:43.156Z" }, + { url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947 }, ] [[package]] name = "rapidfuzz" version = "3.14.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900, upload-time = "2025-11-01T11:54:52.321Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306, upload-time = "2025-11-01T11:53:06.452Z" }, - { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788, upload-time = "2025-11-01T11:53:08.721Z" }, - { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580, upload-time = "2025-11-01T11:53:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947, upload-time = "2025-11-01T11:53:12.093Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872, upload-time = "2025-11-01T11:53:13.664Z" }, - { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512, upload-time = "2025-11-01T11:53:15.109Z" }, - { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398, upload-time = "2025-11-01T11:53:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416, upload-time = "2025-11-01T11:53:19.34Z" }, - { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527, upload-time = "2025-11-01T11:53:20.949Z" }, - { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989, upload-time = "2025-11-01T11:53:22.428Z" }, - { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161, upload-time = "2025-11-01T11:53:23.811Z" }, - { url = "https://files.pythonhosted.org/packages/e4/4f/0d94d09646853bd26978cb3a7541b6233c5760687777fa97da8de0d9a6ac/rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae", size = 1939646, upload-time = "2025-11-01T11:53:25.292Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/f96aefc00f3bbdbab9c0657363ea8437a207d7545ac1c3789673e05d80bd/rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff", size = 1385512, upload-time = "2025-11-01T11:53:27.594Z" }, - { url = "https://files.pythonhosted.org/packages/26/34/71c4f7749c12ee223dba90017a5947e8f03731a7cc9f489b662a8e9e643d/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457", size = 1373571, upload-time = "2025-11-01T11:53:29.096Z" }, - { url = "https://files.pythonhosted.org/packages/32/00/ec8597a64f2be301ce1ee3290d067f49f6a7afb226b67d5f15b56d772ba5/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c", size = 3156759, upload-time = "2025-11-01T11:53:30.777Z" }, - { url = "https://files.pythonhosted.org/packages/61/d5/b41eeb4930501cc899d5a9a7b5c9a33d85a670200d7e81658626dcc0ecc0/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e", size = 1222067, upload-time = "2025-11-01T11:53:32.334Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7d/6d9abb4ffd1027c6ed837b425834f3bed8344472eb3a503ab55b3407c721/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10", size = 2394775, upload-time = "2025-11-01T11:53:34.24Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/4f3ab4c401c5a55364da1ffff8cc879fc97b4e5f4fa96033827da491a973/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41", size = 2526123, upload-time = "2025-11-01T11:53:35.779Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4b/54f804975376a328f57293bd817c12c9036171d15cf7292032e3f5820b2d/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0", size = 4262874, upload-time = "2025-11-01T11:53:37.866Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b6/958db27d8a29a50ee6edd45d33debd3ce732e7209183a72f57544cd5fe22/rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63", size = 1707972, upload-time = "2025-11-01T11:53:39.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/75/fde1f334b0cec15b5946d9f84d73250fbfcc73c236b4bc1b25129d90876b/rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c", size = 1537011, upload-time = "2025-11-01T11:53:40.92Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d7/d83fe001ce599dc7ead57ba1debf923dc961b6bdce522b741e6b8c82f55c/rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0", size = 810744, upload-time = "2025-11-01T11:53:42.723Z" }, - { url = "https://files.pythonhosted.org/packages/92/13/a486369e63ff3c1a58444d16b15c5feb943edd0e6c28a1d7d67cb8946b8f/rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424", size = 1967702, upload-time = "2025-11-01T11:53:44.554Z" }, - { url = "https://files.pythonhosted.org/packages/f1/82/efad25e260b7810f01d6b69122685e355bed78c94a12784bac4e0beb2afb/rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e", size = 1410702, upload-time = "2025-11-01T11:53:46.066Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1a/34c977b860cde91082eae4a97ae503f43e0d84d4af301d857679b66f9869/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8", size = 1382337, upload-time = "2025-11-01T11:53:47.62Z" }, - { url = "https://files.pythonhosted.org/packages/88/74/f50ea0e24a5880a9159e8fd256b84d8f4634c2f6b4f98028bdd31891d907/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519", size = 3165563, upload-time = "2025-11-01T11:53:49.216Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7a/e744359404d7737049c26099423fc54bcbf303de5d870d07d2fb1410f567/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a", size = 1214727, upload-time = "2025-11-01T11:53:50.883Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2e/87adfe14ce75768ec6c2b8acd0e05e85e84be4be5e3d283cdae360afc4fe/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897", size = 2403349, upload-time = "2025-11-01T11:53:52.322Z" }, - { url = "https://files.pythonhosted.org/packages/70/17/6c0b2b2bff9c8b12e12624c07aa22e922b0c72a490f180fa9183d1ef2c75/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58", size = 2507596, upload-time = "2025-11-01T11:53:53.835Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d1/87852a7cbe4da7b962174c749a47433881a63a817d04f3e385ea9babcd9e/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f", size = 4273595, upload-time = "2025-11-01T11:53:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ab/1d0354b7d1771a28fa7fe089bc23acec2bdd3756efa2419f463e3ed80e16/rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204", size = 1757773, upload-time = "2025-11-01T11:53:57.628Z" }, - { url = "https://files.pythonhosted.org/packages/0b/0c/71ef356adc29e2bdf74cd284317b34a16b80258fa0e7e242dd92cc1e6d10/rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15", size = 1576797, upload-time = "2025-11-01T11:53:59.455Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d2/0e64fc27bb08d4304aa3d11154eb5480bcf5d62d60140a7ee984dc07468a/rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317", size = 829940, upload-time = "2025-11-01T11:54:01.1Z" }, - { url = "https://files.pythonhosted.org/packages/32/6f/1b88aaeade83abc5418788f9e6b01efefcd1a69d65ded37d89cd1662be41/rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea", size = 1942086, upload-time = "2025-11-01T11:54:02.592Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2c/b23861347436cb10f46c2bd425489ec462790faaa360a54a7ede5f78de88/rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6", size = 1386993, upload-time = "2025-11-01T11:54:04.12Z" }, - { url = "https://files.pythonhosted.org/packages/83/86/5d72e2c060aa1fbdc1f7362d938f6b237dff91f5b9fc5dd7cc297e112250/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4", size = 1379126, upload-time = "2025-11-01T11:54:05.777Z" }, - { url = "https://files.pythonhosted.org/packages/c9/bc/ef2cee3e4d8b3fc22705ff519f0d487eecc756abdc7c25d53686689d6cf2/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1", size = 3159304, upload-time = "2025-11-01T11:54:07.351Z" }, - { url = "https://files.pythonhosted.org/packages/a0/36/dc5f2f62bbc7bc90be1f75eeaf49ed9502094bb19290dfb4747317b17f12/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421", size = 1218207, upload-time = "2025-11-01T11:54:09.641Z" }, - { url = "https://files.pythonhosted.org/packages/df/7e/8f4be75c1bc62f47edf2bbbe2370ee482fae655ebcc4718ac3827ead3904/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b", size = 2401245, upload-time = "2025-11-01T11:54:11.543Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/f7c92759e1bb188dd05b80d11c630ba59b8d7856657baf454ff56059c2ab/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c", size = 2518308, upload-time = "2025-11-01T11:54:13.134Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ac/85820f70fed5ecb5f1d9a55f1e1e2090ef62985ef41db289b5ac5ec56e28/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a", size = 4265011, upload-time = "2025-11-01T11:54:15.087Z" }, - { url = "https://files.pythonhosted.org/packages/46/a9/616930721ea9835c918af7cde22bff17f9db3639b0c1a7f96684be7f5630/rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3", size = 1742245, upload-time = "2025-11-01T11:54:17.19Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/f2fa5e9635b1ccafda4accf0e38246003f69982d7c81f2faa150014525a4/rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9", size = 1584856, upload-time = "2025-11-01T11:54:18.764Z" }, - { url = "https://files.pythonhosted.org/packages/ef/97/09e20663917678a6d60d8e0e29796db175b1165e2079830430342d5298be/rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583", size = 833490, upload-time = "2025-11-01T11:54:20.753Z" }, - { url = "https://files.pythonhosted.org/packages/03/1b/6b6084576ba87bf21877c77218a0c97ba98cb285b0c02eaaee3acd7c4513/rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50", size = 1968658, upload-time = "2025-11-01T11:54:22.25Z" }, - { url = "https://files.pythonhosted.org/packages/38/c0/fb02a0db80d95704b0a6469cc394e8c38501abf7e1c0b2afe3261d1510c2/rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296", size = 1410742, upload-time = "2025-11-01T11:54:23.863Z" }, - { url = "https://files.pythonhosted.org/packages/a4/72/3fbf12819fc6afc8ec75a45204013b40979d068971e535a7f3512b05e765/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655", size = 1382810, upload-time = "2025-11-01T11:54:25.571Z" }, - { url = "https://files.pythonhosted.org/packages/0f/18/0f1991d59bb7eee28922a00f79d83eafa8c7bfb4e8edebf4af2a160e7196/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1", size = 3166349, upload-time = "2025-11-01T11:54:27.195Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f0/baa958b1989c8f88c78bbb329e969440cf330b5a01a982669986495bb980/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7", size = 1214994, upload-time = "2025-11-01T11:54:28.821Z" }, - { url = "https://files.pythonhosted.org/packages/e4/a0/cd12ec71f9b2519a3954febc5740291cceabc64c87bc6433afcb36259f3b/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf", size = 2403919, upload-time = "2025-11-01T11:54:30.393Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ce/019bd2176c1644098eced4f0595cb4b3ef52e4941ac9a5854f209d0a6e16/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785", size = 2508346, upload-time = "2025-11-01T11:54:32.048Z" }, - { url = "https://files.pythonhosted.org/packages/23/f8/be16c68e2c9e6c4f23e8f4adbb7bccc9483200087ed28ff76c5312da9b14/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35", size = 4274105, upload-time = "2025-11-01T11:54:33.701Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d1/5ab148e03f7e6ec8cd220ccf7af74d3aaa4de26dd96df58936beb7cba820/rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad", size = 1793465, upload-time = "2025-11-01T11:54:35.331Z" }, - { url = "https://files.pythonhosted.org/packages/cd/97/433b2d98e97abd9fff1c470a109b311669f44cdec8d0d5aa250aceaed1fb/rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c", size = 1623491, upload-time = "2025-11-01T11:54:38.085Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/e2176eb94f94892441bce3ddc514c179facb65db245e7ce3356965595b19/rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253", size = 851487, upload-time = "2025-11-01T11:54:40.176Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306 }, + { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788 }, + { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580 }, + { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947 }, + { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872 }, + { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512 }, + { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398 }, + { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416 }, + { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527 }, + { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989 }, + { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161 }, + { url = "https://files.pythonhosted.org/packages/e4/4f/0d94d09646853bd26978cb3a7541b6233c5760687777fa97da8de0d9a6ac/rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae", size = 1939646 }, + { url = "https://files.pythonhosted.org/packages/b6/eb/f96aefc00f3bbdbab9c0657363ea8437a207d7545ac1c3789673e05d80bd/rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff", size = 1385512 }, + { url = "https://files.pythonhosted.org/packages/26/34/71c4f7749c12ee223dba90017a5947e8f03731a7cc9f489b662a8e9e643d/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457", size = 1373571 }, + { url = "https://files.pythonhosted.org/packages/32/00/ec8597a64f2be301ce1ee3290d067f49f6a7afb226b67d5f15b56d772ba5/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c", size = 3156759 }, + { url = "https://files.pythonhosted.org/packages/61/d5/b41eeb4930501cc899d5a9a7b5c9a33d85a670200d7e81658626dcc0ecc0/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e", size = 1222067 }, + { url = "https://files.pythonhosted.org/packages/2a/7d/6d9abb4ffd1027c6ed837b425834f3bed8344472eb3a503ab55b3407c721/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10", size = 2394775 }, + { url = "https://files.pythonhosted.org/packages/15/ce/4f3ab4c401c5a55364da1ffff8cc879fc97b4e5f4fa96033827da491a973/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41", size = 2526123 }, + { url = "https://files.pythonhosted.org/packages/c1/4b/54f804975376a328f57293bd817c12c9036171d15cf7292032e3f5820b2d/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0", size = 4262874 }, + { url = "https://files.pythonhosted.org/packages/e9/b6/958db27d8a29a50ee6edd45d33debd3ce732e7209183a72f57544cd5fe22/rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63", size = 1707972 }, + { url = "https://files.pythonhosted.org/packages/07/75/fde1f334b0cec15b5946d9f84d73250fbfcc73c236b4bc1b25129d90876b/rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c", size = 1537011 }, + { url = "https://files.pythonhosted.org/packages/2e/d7/d83fe001ce599dc7ead57ba1debf923dc961b6bdce522b741e6b8c82f55c/rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0", size = 810744 }, + { url = "https://files.pythonhosted.org/packages/92/13/a486369e63ff3c1a58444d16b15c5feb943edd0e6c28a1d7d67cb8946b8f/rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424", size = 1967702 }, + { url = "https://files.pythonhosted.org/packages/f1/82/efad25e260b7810f01d6b69122685e355bed78c94a12784bac4e0beb2afb/rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e", size = 1410702 }, + { url = "https://files.pythonhosted.org/packages/ba/1a/34c977b860cde91082eae4a97ae503f43e0d84d4af301d857679b66f9869/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8", size = 1382337 }, + { url = "https://files.pythonhosted.org/packages/88/74/f50ea0e24a5880a9159e8fd256b84d8f4634c2f6b4f98028bdd31891d907/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519", size = 3165563 }, + { url = "https://files.pythonhosted.org/packages/e8/7a/e744359404d7737049c26099423fc54bcbf303de5d870d07d2fb1410f567/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a", size = 1214727 }, + { url = "https://files.pythonhosted.org/packages/d3/2e/87adfe14ce75768ec6c2b8acd0e05e85e84be4be5e3d283cdae360afc4fe/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897", size = 2403349 }, + { url = "https://files.pythonhosted.org/packages/70/17/6c0b2b2bff9c8b12e12624c07aa22e922b0c72a490f180fa9183d1ef2c75/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58", size = 2507596 }, + { url = "https://files.pythonhosted.org/packages/c3/d1/87852a7cbe4da7b962174c749a47433881a63a817d04f3e385ea9babcd9e/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f", size = 4273595 }, + { url = "https://files.pythonhosted.org/packages/c1/ab/1d0354b7d1771a28fa7fe089bc23acec2bdd3756efa2419f463e3ed80e16/rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204", size = 1757773 }, + { url = "https://files.pythonhosted.org/packages/0b/0c/71ef356adc29e2bdf74cd284317b34a16b80258fa0e7e242dd92cc1e6d10/rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15", size = 1576797 }, + { url = "https://files.pythonhosted.org/packages/fe/d2/0e64fc27bb08d4304aa3d11154eb5480bcf5d62d60140a7ee984dc07468a/rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317", size = 829940 }, + { url = "https://files.pythonhosted.org/packages/32/6f/1b88aaeade83abc5418788f9e6b01efefcd1a69d65ded37d89cd1662be41/rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea", size = 1942086 }, + { url = "https://files.pythonhosted.org/packages/a0/2c/b23861347436cb10f46c2bd425489ec462790faaa360a54a7ede5f78de88/rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6", size = 1386993 }, + { url = "https://files.pythonhosted.org/packages/83/86/5d72e2c060aa1fbdc1f7362d938f6b237dff91f5b9fc5dd7cc297e112250/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4", size = 1379126 }, + { url = "https://files.pythonhosted.org/packages/c9/bc/ef2cee3e4d8b3fc22705ff519f0d487eecc756abdc7c25d53686689d6cf2/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1", size = 3159304 }, + { url = "https://files.pythonhosted.org/packages/a0/36/dc5f2f62bbc7bc90be1f75eeaf49ed9502094bb19290dfb4747317b17f12/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421", size = 1218207 }, + { url = "https://files.pythonhosted.org/packages/df/7e/8f4be75c1bc62f47edf2bbbe2370ee482fae655ebcc4718ac3827ead3904/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b", size = 2401245 }, + { url = "https://files.pythonhosted.org/packages/05/38/f7c92759e1bb188dd05b80d11c630ba59b8d7856657baf454ff56059c2ab/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c", size = 2518308 }, + { url = "https://files.pythonhosted.org/packages/c7/ac/85820f70fed5ecb5f1d9a55f1e1e2090ef62985ef41db289b5ac5ec56e28/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a", size = 4265011 }, + { url = "https://files.pythonhosted.org/packages/46/a9/616930721ea9835c918af7cde22bff17f9db3639b0c1a7f96684be7f5630/rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3", size = 1742245 }, + { url = "https://files.pythonhosted.org/packages/06/8a/f2fa5e9635b1ccafda4accf0e38246003f69982d7c81f2faa150014525a4/rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9", size = 1584856 }, + { url = "https://files.pythonhosted.org/packages/ef/97/09e20663917678a6d60d8e0e29796db175b1165e2079830430342d5298be/rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583", size = 833490 }, + { url = "https://files.pythonhosted.org/packages/03/1b/6b6084576ba87bf21877c77218a0c97ba98cb285b0c02eaaee3acd7c4513/rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50", size = 1968658 }, + { url = "https://files.pythonhosted.org/packages/38/c0/fb02a0db80d95704b0a6469cc394e8c38501abf7e1c0b2afe3261d1510c2/rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296", size = 1410742 }, + { url = "https://files.pythonhosted.org/packages/a4/72/3fbf12819fc6afc8ec75a45204013b40979d068971e535a7f3512b05e765/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655", size = 1382810 }, + { url = "https://files.pythonhosted.org/packages/0f/18/0f1991d59bb7eee28922a00f79d83eafa8c7bfb4e8edebf4af2a160e7196/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1", size = 3166349 }, + { url = "https://files.pythonhosted.org/packages/0d/f0/baa958b1989c8f88c78bbb329e969440cf330b5a01a982669986495bb980/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7", size = 1214994 }, + { url = "https://files.pythonhosted.org/packages/e4/a0/cd12ec71f9b2519a3954febc5740291cceabc64c87bc6433afcb36259f3b/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf", size = 2403919 }, + { url = "https://files.pythonhosted.org/packages/0b/ce/019bd2176c1644098eced4f0595cb4b3ef52e4941ac9a5854f209d0a6e16/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785", size = 2508346 }, + { url = "https://files.pythonhosted.org/packages/23/f8/be16c68e2c9e6c4f23e8f4adbb7bccc9483200087ed28ff76c5312da9b14/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35", size = 4274105 }, + { url = "https://files.pythonhosted.org/packages/a1/d1/5ab148e03f7e6ec8cd220ccf7af74d3aaa4de26dd96df58936beb7cba820/rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad", size = 1793465 }, + { url = "https://files.pythonhosted.org/packages/cd/97/433b2d98e97abd9fff1c470a109b311669f44cdec8d0d5aa250aceaed1fb/rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c", size = 1623491 }, + { url = "https://files.pythonhosted.org/packages/e2/f6/e2176eb94f94892441bce3ddc514c179facb65db245e7ce3356965595b19/rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253", size = 851487 }, ] [[package]] @@ -7221,7 +7204,7 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/b8/011338eec8aea40cf9b82da7481f3e65e100537cff4c866b3c1b1e719b97/rapidocr-3.7.0-py3-none-any.whl", hash = "sha256:ace47f037956fa3780875f8556a0f27ab20d91962d36a9a2816aa367bb48718f", size = 15080131, upload-time = "2026-03-04T15:38:20.339Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b8/011338eec8aea40cf9b82da7481f3e65e100537cff4c866b3c1b1e719b97/rapidocr-3.7.0-py3-none-any.whl", hash = "sha256:ace47f037956fa3780875f8556a0f27ab20d91962d36a9a2816aa367bb48718f", size = 15080131 }, ] [[package]] @@ -7233,18 +7216,18 @@ dependencies = [ { name = "nh3" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310, upload-time = "2024-07-08T15:00:56.577Z" }, + { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310 }, ] [[package]] name = "redis" version = "6.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847 }, ] [[package]] @@ -7256,97 +7239,97 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, ] [[package]] name = "regex" version = "2026.2.28" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574, upload-time = "2026-02-28T02:16:50.455Z" }, - { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426, upload-time = "2026-02-28T02:16:52.52Z" }, - { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200, upload-time = "2026-02-28T02:16:54.08Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765, upload-time = "2026-02-28T02:16:55.905Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093, upload-time = "2026-02-28T02:16:58.094Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455, upload-time = "2026-02-28T02:17:00.918Z" }, - { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037, upload-time = "2026-02-28T02:17:02.842Z" }, - { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113, upload-time = "2026-02-28T02:17:04.506Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194, upload-time = "2026-02-28T02:17:06.888Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846, upload-time = "2026-02-28T02:17:09.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516, upload-time = "2026-02-28T02:17:11.004Z" }, - { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278, upload-time = "2026-02-28T02:17:12.693Z" }, - { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068, upload-time = "2026-02-28T02:17:14.9Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416, upload-time = "2026-02-28T02:17:17.15Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297, upload-time = "2026-02-28T02:17:18.723Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408, upload-time = "2026-02-28T02:17:20.328Z" }, - { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311, upload-time = "2026-02-28T02:17:22.591Z" }, - { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285, upload-time = "2026-02-28T02:17:24.355Z" }, - { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051, upload-time = "2026-02-28T02:17:26.722Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842, upload-time = "2026-02-28T02:17:29.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083, upload-time = "2026-02-28T02:17:31.363Z" }, - { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412, upload-time = "2026-02-28T02:17:33.248Z" }, - { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101, upload-time = "2026-02-28T02:17:35.053Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260, upload-time = "2026-02-28T02:17:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311, upload-time = "2026-02-28T02:17:39.855Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876, upload-time = "2026-02-28T02:17:42.317Z" }, - { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632, upload-time = "2026-02-28T02:17:45.073Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320, upload-time = "2026-02-28T02:17:47.192Z" }, - { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152, upload-time = "2026-02-28T02:17:49.067Z" }, - { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398, upload-time = "2026-02-28T02:17:50.744Z" }, - { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282, upload-time = "2026-02-28T02:17:53.074Z" }, - { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382, upload-time = "2026-02-28T02:17:54.888Z" }, - { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541, upload-time = "2026-02-28T02:17:56.813Z" }, - { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984, upload-time = "2026-02-28T02:17:58.538Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509, upload-time = "2026-02-28T02:18:00.208Z" }, - { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429, upload-time = "2026-02-28T02:18:02.328Z" }, - { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422, upload-time = "2026-02-28T02:18:04.23Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175, upload-time = "2026-02-28T02:18:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044, upload-time = "2026-02-28T02:18:08.736Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056, upload-time = "2026-02-28T02:18:10.777Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743, upload-time = "2026-02-28T02:18:13.025Z" }, - { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633, upload-time = "2026-02-28T02:18:16.84Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862, upload-time = "2026-02-28T02:18:18.892Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788, upload-time = "2026-02-28T02:18:21.475Z" }, - { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184, upload-time = "2026-02-28T02:18:23.492Z" }, - { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137, upload-time = "2026-02-28T02:18:25.375Z" }, - { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682, upload-time = "2026-02-28T02:18:27.205Z" }, - { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735, upload-time = "2026-02-28T02:18:29.015Z" }, - { url = "https://files.pythonhosted.org/packages/cf/03/691015f7a7cb1ed6dacb2ea5de5682e4858e05a4c5506b2839cd533bbcd6/regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc", size = 489497, upload-time = "2026-02-28T02:18:30.889Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ba/8db8fd19afcbfa0e1036eaa70c05f20ca8405817d4ad7a38a6b4c2f031ac/regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd", size = 291295, upload-time = "2026-02-28T02:18:33.426Z" }, - { url = "https://files.pythonhosted.org/packages/5a/79/9aa0caf089e8defef9b857b52fc53801f62ff868e19e5c83d4a96612eba1/regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff", size = 289275, upload-time = "2026-02-28T02:18:35.247Z" }, - { url = "https://files.pythonhosted.org/packages/eb/26/ee53117066a30ef9c883bf1127eece08308ccf8ccd45c45a966e7a665385/regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911", size = 797176, upload-time = "2026-02-28T02:18:37.15Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/67fb0495a97259925f343ae78b5d24d4a6624356ae138b57f18bd43006e4/regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33", size = 863813, upload-time = "2026-02-28T02:18:39.478Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/93ac9bbafc53618091c685c7ed40239a90bf9f2a82c983f0baa97cb7ae07/regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117", size = 908678, upload-time = "2026-02-28T02:18:41.619Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/a8f5e0561702b25239846a16349feece59712ae20598ebb205580332a471/regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d", size = 801528, upload-time = "2026-02-28T02:18:43.624Z" }, - { url = "https://files.pythonhosted.org/packages/96/5d/ed6d4cbde80309854b1b9f42d9062fee38ade15f7eb4909f6ef2440403b5/regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a", size = 775373, upload-time = "2026-02-28T02:18:46.102Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e9/6e53c34e8068b9deec3e87210086ecb5b9efebdefca6b0d3fa43d66dcecb/regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf", size = 784859, upload-time = "2026-02-28T02:18:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/48/3c/736e1c7ca7f0dcd2ae33819888fdc69058a349b7e5e84bc3e2f296bbf794/regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952", size = 857813, upload-time = "2026-02-28T02:18:50.576Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7c/48c4659ad9da61f58e79dbe8c05223e0006696b603c16eb6b5cbfbb52c27/regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8", size = 763705, upload-time = "2026-02-28T02:18:52.59Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a1/bc1c261789283128165f71b71b4b221dd1b79c77023752a6074c102f18d8/regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07", size = 848734, upload-time = "2026-02-28T02:18:54.595Z" }, - { url = "https://files.pythonhosted.org/packages/10/d8/979407faf1397036e25a5ae778157366a911c0f382c62501009f4957cf86/regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6", size = 789871, upload-time = "2026-02-28T02:18:57.34Z" }, - { url = "https://files.pythonhosted.org/packages/03/23/da716821277115fcb1f4e3de1e5dc5023a1e6533598c486abf5448612579/regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6", size = 271825, upload-time = "2026-02-28T02:18:59.202Z" }, - { url = "https://files.pythonhosted.org/packages/91/ff/90696f535d978d5f16a52a419be2770a8d8a0e7e0cfecdbfc31313df7fab/regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7", size = 280548, upload-time = "2026-02-28T02:19:01.049Z" }, - { url = "https://files.pythonhosted.org/packages/69/f9/5e1b5652fc0af3fcdf7677e7df3ad2a0d47d669b34ac29a63bb177bb731b/regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d", size = 273444, upload-time = "2026-02-28T02:19:03.255Z" }, - { url = "https://files.pythonhosted.org/packages/d3/eb/8389f9e940ac89bcf58d185e230a677b4fd07c5f9b917603ad5c0f8fa8fe/regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e", size = 492546, upload-time = "2026-02-28T02:19:05.378Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c7/09441d27ce2a6fa6a61ea3150ea4639c1dcda9b31b2ea07b80d6937b24dd/regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c", size = 292986, upload-time = "2026-02-28T02:19:07.24Z" }, - { url = "https://files.pythonhosted.org/packages/fb/69/4144b60ed7760a6bd235e4087041f487aa4aa62b45618ce018b0c14833ea/regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7", size = 291518, upload-time = "2026-02-28T02:19:09.698Z" }, - { url = "https://files.pythonhosted.org/packages/2d/be/77e5426cf5948c82f98c53582009ca9e94938c71f73a8918474f2e2990bb/regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e", size = 809464, upload-time = "2026-02-28T02:19:12.494Z" }, - { url = "https://files.pythonhosted.org/packages/45/99/2c8c5ac90dc7d05c6e7d8e72c6a3599dc08cd577ac476898e91ca787d7f1/regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc", size = 869553, upload-time = "2026-02-28T02:19:15.151Z" }, - { url = "https://files.pythonhosted.org/packages/53/34/daa66a342f0271e7737003abf6c3097aa0498d58c668dbd88362ef94eb5d/regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8", size = 915289, upload-time = "2026-02-28T02:19:17.331Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c7/e22c2aaf0a12e7e22ab19b004bb78d32ca1ecc7ef245949935463c5567de/regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0", size = 812156, upload-time = "2026-02-28T02:19:20.011Z" }, - { url = "https://files.pythonhosted.org/packages/7f/bb/2dc18c1efd9051cf389cd0d7a3a4d90f6804b9fff3a51b5dc3c85b935f71/regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b", size = 782215, upload-time = "2026-02-28T02:19:22.047Z" }, - { url = "https://files.pythonhosted.org/packages/17/1e/9e4ec9b9013931faa32226ec4aa3c71fe664a6d8a2b91ac56442128b332f/regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b", size = 798925, upload-time = "2026-02-28T02:19:24.173Z" }, - { url = "https://files.pythonhosted.org/packages/71/57/a505927e449a9ccb41e2cc8d735e2abe3444b0213d1cf9cb364a8c1f2524/regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033", size = 864701, upload-time = "2026-02-28T02:19:26.376Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ad/c62cb60cdd93e13eac5b3d9d6bd5d284225ed0e3329426f94d2552dd7cca/regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43", size = 770899, upload-time = "2026-02-28T02:19:29.38Z" }, - { url = "https://files.pythonhosted.org/packages/3c/5a/874f861f5c3d5ab99633e8030dee1bc113db8e0be299d1f4b07f5b5ec349/regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18", size = 854727, upload-time = "2026-02-28T02:19:31.494Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ca/d2c03b0efde47e13db895b975b2be6a73ed90b8ba963677927283d43bf74/regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a", size = 800366, upload-time = "2026-02-28T02:19:34.248Z" }, - { url = "https://files.pythonhosted.org/packages/14/bd/ee13b20b763b8989f7c75d592bfd5de37dc1181814a2a2747fedcf97e3ba/regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e", size = 274936, upload-time = "2026-02-28T02:19:36.313Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e7/d8020e39414c93af7f0d8688eabcecece44abfd5ce314b21dfda0eebd3d8/regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9", size = 284779, upload-time = "2026-02-28T02:19:38.625Z" }, - { url = "https://files.pythonhosted.org/packages/13/c0/ad225f4a405827486f1955283407cf758b6d2fb966712644c5f5aef33d1b/regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec", size = 275010, upload-time = "2026-02-28T02:19:40.65Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574 }, + { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426 }, + { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200 }, + { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765 }, + { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093 }, + { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455 }, + { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037 }, + { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194 }, + { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846 }, + { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516 }, + { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278 }, + { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068 }, + { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416 }, + { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297 }, + { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408 }, + { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311 }, + { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285 }, + { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051 }, + { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842 }, + { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083 }, + { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412 }, + { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101 }, + { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311 }, + { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876 }, + { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632 }, + { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320 }, + { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152 }, + { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398 }, + { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282 }, + { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382 }, + { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541 }, + { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984 }, + { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509 }, + { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429 }, + { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422 }, + { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175 }, + { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044 }, + { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056 }, + { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743 }, + { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633 }, + { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862 }, + { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788 }, + { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184 }, + { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137 }, + { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682 }, + { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735 }, + { url = "https://files.pythonhosted.org/packages/cf/03/691015f7a7cb1ed6dacb2ea5de5682e4858e05a4c5506b2839cd533bbcd6/regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc", size = 489497 }, + { url = "https://files.pythonhosted.org/packages/c6/ba/8db8fd19afcbfa0e1036eaa70c05f20ca8405817d4ad7a38a6b4c2f031ac/regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd", size = 291295 }, + { url = "https://files.pythonhosted.org/packages/5a/79/9aa0caf089e8defef9b857b52fc53801f62ff868e19e5c83d4a96612eba1/regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff", size = 289275 }, + { url = "https://files.pythonhosted.org/packages/eb/26/ee53117066a30ef9c883bf1127eece08308ccf8ccd45c45a966e7a665385/regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911", size = 797176 }, + { url = "https://files.pythonhosted.org/packages/05/1b/67fb0495a97259925f343ae78b5d24d4a6624356ae138b57f18bd43006e4/regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33", size = 863813 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/93ac9bbafc53618091c685c7ed40239a90bf9f2a82c983f0baa97cb7ae07/regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117", size = 908678 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/a8f5e0561702b25239846a16349feece59712ae20598ebb205580332a471/regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d", size = 801528 }, + { url = "https://files.pythonhosted.org/packages/96/5d/ed6d4cbde80309854b1b9f42d9062fee38ade15f7eb4909f6ef2440403b5/regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a", size = 775373 }, + { url = "https://files.pythonhosted.org/packages/6a/e9/6e53c34e8068b9deec3e87210086ecb5b9efebdefca6b0d3fa43d66dcecb/regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf", size = 784859 }, + { url = "https://files.pythonhosted.org/packages/48/3c/736e1c7ca7f0dcd2ae33819888fdc69058a349b7e5e84bc3e2f296bbf794/regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952", size = 857813 }, + { url = "https://files.pythonhosted.org/packages/6e/7c/48c4659ad9da61f58e79dbe8c05223e0006696b603c16eb6b5cbfbb52c27/regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8", size = 763705 }, + { url = "https://files.pythonhosted.org/packages/cf/a1/bc1c261789283128165f71b71b4b221dd1b79c77023752a6074c102f18d8/regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07", size = 848734 }, + { url = "https://files.pythonhosted.org/packages/10/d8/979407faf1397036e25a5ae778157366a911c0f382c62501009f4957cf86/regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6", size = 789871 }, + { url = "https://files.pythonhosted.org/packages/03/23/da716821277115fcb1f4e3de1e5dc5023a1e6533598c486abf5448612579/regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6", size = 271825 }, + { url = "https://files.pythonhosted.org/packages/91/ff/90696f535d978d5f16a52a419be2770a8d8a0e7e0cfecdbfc31313df7fab/regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7", size = 280548 }, + { url = "https://files.pythonhosted.org/packages/69/f9/5e1b5652fc0af3fcdf7677e7df3ad2a0d47d669b34ac29a63bb177bb731b/regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d", size = 273444 }, + { url = "https://files.pythonhosted.org/packages/d3/eb/8389f9e940ac89bcf58d185e230a677b4fd07c5f9b917603ad5c0f8fa8fe/regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e", size = 492546 }, + { url = "https://files.pythonhosted.org/packages/7b/c7/09441d27ce2a6fa6a61ea3150ea4639c1dcda9b31b2ea07b80d6937b24dd/regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c", size = 292986 }, + { url = "https://files.pythonhosted.org/packages/fb/69/4144b60ed7760a6bd235e4087041f487aa4aa62b45618ce018b0c14833ea/regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7", size = 291518 }, + { url = "https://files.pythonhosted.org/packages/2d/be/77e5426cf5948c82f98c53582009ca9e94938c71f73a8918474f2e2990bb/regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e", size = 809464 }, + { url = "https://files.pythonhosted.org/packages/45/99/2c8c5ac90dc7d05c6e7d8e72c6a3599dc08cd577ac476898e91ca787d7f1/regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc", size = 869553 }, + { url = "https://files.pythonhosted.org/packages/53/34/daa66a342f0271e7737003abf6c3097aa0498d58c668dbd88362ef94eb5d/regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8", size = 915289 }, + { url = "https://files.pythonhosted.org/packages/c5/c7/e22c2aaf0a12e7e22ab19b004bb78d32ca1ecc7ef245949935463c5567de/regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0", size = 812156 }, + { url = "https://files.pythonhosted.org/packages/7f/bb/2dc18c1efd9051cf389cd0d7a3a4d90f6804b9fff3a51b5dc3c85b935f71/regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b", size = 782215 }, + { url = "https://files.pythonhosted.org/packages/17/1e/9e4ec9b9013931faa32226ec4aa3c71fe664a6d8a2b91ac56442128b332f/regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b", size = 798925 }, + { url = "https://files.pythonhosted.org/packages/71/57/a505927e449a9ccb41e2cc8d735e2abe3444b0213d1cf9cb364a8c1f2524/regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033", size = 864701 }, + { url = "https://files.pythonhosted.org/packages/a6/ad/c62cb60cdd93e13eac5b3d9d6bd5d284225ed0e3329426f94d2552dd7cca/regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43", size = 770899 }, + { url = "https://files.pythonhosted.org/packages/3c/5a/874f861f5c3d5ab99633e8030dee1bc113db8e0be299d1f4b07f5b5ec349/regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18", size = 854727 }, + { url = "https://files.pythonhosted.org/packages/6b/ca/d2c03b0efde47e13db895b975b2be6a73ed90b8ba963677927283d43bf74/regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a", size = 800366 }, + { url = "https://files.pythonhosted.org/packages/14/bd/ee13b20b763b8989f7c75d592bfd5de37dc1181814a2a2747fedcf97e3ba/regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e", size = 274936 }, + { url = "https://files.pythonhosted.org/packages/cb/e7/d8020e39414c93af7f0d8688eabcecece44abfd5ce314b21dfda0eebd3d8/regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9", size = 284779 }, + { url = "https://files.pythonhosted.org/packages/13/c0/ad225f4a405827486f1955283407cf758b6d2fb966712644c5f5aef33d1b/regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec", size = 275010 }, ] [[package]] @@ -7359,9 +7342,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017 }, ] [[package]] @@ -7372,9 +7355,9 @@ dependencies = [ { name = "oauthlib" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 }, ] [[package]] @@ -7384,18 +7367,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, ] [[package]] name = "rerankers" version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/1e/3ed2026be7c135939905eac4f50d1bf8339180821c6757b2e91b83de2fa5/rerankers-0.10.0.tar.gz", hash = "sha256:b8e8b363abc4e9757151956949c27b197993c0a774437287a932f855afc17a73", size = 49679, upload-time = "2025-05-22T08:22:53.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/1e/3ed2026be7c135939905eac4f50d1bf8339180821c6757b2e91b83de2fa5/rerankers-0.10.0.tar.gz", hash = "sha256:b8e8b363abc4e9757151956949c27b197993c0a774437287a932f855afc17a73", size = 49679 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/ed/f3b81ca8743d69b95d679b95e6e1d22cb7cc678ae77c6a57827303a7e48c/rerankers-0.10.0-py3-none-any.whl", hash = "sha256:634a6befa130a245ed46022ade217ee482869448f01aae2051ed54d7d5bd2791", size = 53084, upload-time = "2025-05-22T08:22:52.022Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/f3b81ca8743d69b95d679b95e6e1d22cb7cc678ae77c6a57827303a7e48c/rerankers-0.10.0-py3-none-any.whl", hash = "sha256:634a6befa130a245ed46022ade217ee482869448f01aae2051ed54d7d5bd2791", size = 53084 }, ] [package.optional-dependencies] @@ -7407,9 +7390,9 @@ flashrank = [ name = "rfc3986" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 }, ] [[package]] @@ -7420,131 +7403,131 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458 }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887 }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904 }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945 }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783 }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021 }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589 }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025 }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895 }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799 }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731 }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027 }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020 }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224 }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645 }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443 }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375 }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850 }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812 }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841 }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149 }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843 }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507 }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949 }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790 }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217 }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806 }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341 }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768 }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099 }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192 }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080 }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841 }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670 }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005 }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112 }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049 }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661 }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606 }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126 }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371 }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604 }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391 }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868 }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747 }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795 }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330 }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194 }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340 }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765 }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834 }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470 }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630 }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148 }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030 }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570 }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532 }, ] [[package]] name = "rtree" version = "1.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/09/7302695875a019514de9a5dd17b8320e7a19d6e7bc8f85dcfb79a4ce2da3/rtree-1.4.1.tar.gz", hash = "sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46", size = 52425, upload-time = "2025-08-13T19:32:01.413Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/09/7302695875a019514de9a5dd17b8320e7a19d6e7bc8f85dcfb79a4ce2da3/rtree-1.4.1.tar.gz", hash = "sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46", size = 52425 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/d9/108cd989a4c0954e60b3cdc86fd2826407702b5375f6dfdab2802e5fed98/rtree-1.4.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4", size = 468484, upload-time = "2025-08-13T19:31:50.593Z" }, - { url = "https://files.pythonhosted.org/packages/f3/cf/2710b6fd6b07ea0aef317b29f335790ba6adf06a28ac236078ed9bd8a91d/rtree-1.4.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d", size = 436325, upload-time = "2025-08-13T19:31:52.367Z" }, - { url = "https://files.pythonhosted.org/packages/55/e1/4d075268a46e68db3cac51846eb6a3ab96ed481c585c5a1ad411b3c23aad/rtree-1.4.1-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65", size = 459789, upload-time = "2025-08-13T19:31:53.926Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/e5d44be90525cd28503e7f836d077ae6663ec0687a13ba7810b4114b3668/rtree-1.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c", size = 507644, upload-time = "2025-08-13T19:31:55.164Z" }, - { url = "https://files.pythonhosted.org/packages/fd/85/b8684f769a142163b52859a38a486493b05bafb4f2fb71d4f945de28ebf9/rtree-1.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967", size = 1454478, upload-time = "2025-08-13T19:31:56.808Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a4/c2292b95246b9165cc43a0c3757e80995d58bc9b43da5cb47ad6e3535213/rtree-1.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc", size = 1555140, upload-time = "2025-08-13T19:31:58.031Z" }, - { url = "https://files.pythonhosted.org/packages/74/25/5282c8270bfcd620d3e73beb35b40ac4ab00f0a898d98ebeb41ef0989ec8/rtree-1.4.1-py3-none-win_amd64.whl", hash = "sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489", size = 389358, upload-time = "2025-08-13T19:31:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253, upload-time = "2025-08-13T19:32:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/04/d9/108cd989a4c0954e60b3cdc86fd2826407702b5375f6dfdab2802e5fed98/rtree-1.4.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4", size = 468484 }, + { url = "https://files.pythonhosted.org/packages/f3/cf/2710b6fd6b07ea0aef317b29f335790ba6adf06a28ac236078ed9bd8a91d/rtree-1.4.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d", size = 436325 }, + { url = "https://files.pythonhosted.org/packages/55/e1/4d075268a46e68db3cac51846eb6a3ab96ed481c585c5a1ad411b3c23aad/rtree-1.4.1-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65", size = 459789 }, + { url = "https://files.pythonhosted.org/packages/d1/75/e5d44be90525cd28503e7f836d077ae6663ec0687a13ba7810b4114b3668/rtree-1.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c", size = 507644 }, + { url = "https://files.pythonhosted.org/packages/fd/85/b8684f769a142163b52859a38a486493b05bafb4f2fb71d4f945de28ebf9/rtree-1.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967", size = 1454478 }, + { url = "https://files.pythonhosted.org/packages/e9/a4/c2292b95246b9165cc43a0c3757e80995d58bc9b43da5cb47ad6e3535213/rtree-1.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc", size = 1555140 }, + { url = "https://files.pythonhosted.org/packages/74/25/5282c8270bfcd620d3e73beb35b40ac4ab00f0a898d98ebeb41ef0989ec8/rtree-1.4.1-py3-none-win_amd64.whl", hash = "sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489", size = 389358 }, + { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253 }, ] [[package]] name = "ruff" version = "0.15.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394 }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693 }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044 }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135 }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041 }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987 }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057 }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613 }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557 }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440 }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963 }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484 }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426 }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125 }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959 }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893 }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175 }, ] [[package]] @@ -7554,31 +7537,31 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830 }, ] [[package]] name = "safetensors" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, - { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, - { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, - { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, - { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, + { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781 }, + { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058 }, + { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748 }, + { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881 }, + { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463 }, + { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855 }, + { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152 }, + { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856 }, + { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060 }, + { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715 }, + { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377 }, + { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368 }, + { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423 }, + { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380 }, ] [package.optional-dependencies] @@ -7598,38 +7581,38 @@ dependencies = [ { name = "scipy" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, - { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, - { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, - { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, - { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, - { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, - { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, - { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, - { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, - { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, - { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, - { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, - { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, - { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, - { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, - { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, - { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242 }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075 }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492 }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904 }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359 }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898 }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770 }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458 }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341 }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022 }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409 }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760 }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045 }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324 }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651 }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045 }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994 }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518 }, + { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667 }, + { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524 }, + { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133 }, + { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223 }, + { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518 }, + { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546 }, + { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305 }, + { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257 }, + { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673 }, + { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467 }, + { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395 }, + { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647 }, ] [[package]] @@ -7639,58 +7622,58 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, ] [[package]] @@ -7701,9 +7684,9 @@ dependencies = [ { name = "cryptography", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "jeepney", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554 }, ] [[package]] @@ -7714,9 +7697,9 @@ dependencies = [ { name = "csvw" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/57/85cac3a8e32370e88fa5fa92812edb6025db7fcbed51452bd56ee1524957/segments-2.4.0.tar.gz", hash = "sha256:bba71f5520ddd54c8aa2f4d765a60618c6862162d6e7356a4a097f2223166f5b", size = 18662, upload-time = "2026-03-07T10:01:28.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/57/85cac3a8e32370e88fa5fa92812edb6025db7fcbed51452bd56ee1524957/segments-2.4.0.tar.gz", hash = "sha256:bba71f5520ddd54c8aa2f4d765a60618c6862162d6e7356a4a097f2223166f5b", size = 18662 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/60/eef9acce946177f92c9aabf432224d87ab908bafafac516a36ab924199f3/segments-2.4.0-py2.py3-none-any.whl", hash = "sha256:4021dc67f201cc03c864c74c618bdb163b1af629da3040babbaa37d8813f3db0", size = 16321, upload-time = "2026-03-07T10:01:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/be/60/eef9acce946177f92c9aabf432224d87ab908bafafac516a36ab924199f3/segments-2.4.0-py2.py3-none-any.whl", hash = "sha256:4021dc67f201cc03c864c74c618bdb163b1af629da3040babbaa37d8813f3db0", size = 16321 }, ] [[package]] @@ -7727,9 +7710,9 @@ dependencies = [ { name = "mpire", extra = ["dill"] }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/a0/ce7e3d6cc76498fd594e667d10a03f17d7cced129e46869daec23523bf5a/semchunk-3.2.5.tar.gz", hash = "sha256:ee15e9a06a69a411937dd8fcf0a25d7ef389c5195863140436872a02c95b0218", size = 17667, upload-time = "2025-10-28T02:12:38.025Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/a0/ce7e3d6cc76498fd594e667d10a03f17d7cced129e46869daec23523bf5a/semchunk-3.2.5.tar.gz", hash = "sha256:ee15e9a06a69a411937dd8fcf0a25d7ef389c5195863140436872a02c95b0218", size = 17667 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/95/12d226ee4d207cb1f77a216baa7e1a8bae2639733c140abe8d0316d23a18/semchunk-3.2.5-py3-none-any.whl", hash = "sha256:fd09cc5f380bd010b8ca773bd81893f7eaf11d37dd8362a83d46cedaf5dae076", size = 13048, upload-time = "2025-10-28T02:12:36.724Z" }, + { url = "https://files.pythonhosted.org/packages/f8/95/12d226ee4d207cb1f77a216baa7e1a8bae2639733c140abe8d0316d23a18/semchunk-3.2.5-py3-none-any.whl", hash = "sha256:fd09cc5f380bd010b8ca773bd81893f7eaf11d37dd8362a83d46cedaf5dae076", size = 13048 }, ] [[package]] @@ -7746,18 +7729,18 @@ dependencies = [ { name = "transformers" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/26/448453925b6ce0c29d8b54327caa71ee4835511aef02070467402273079c/sentence_transformers-5.3.0.tar.gz", hash = "sha256:414a0a881f53a4df0e6cbace75f823bfcb6b94d674c42a384b498959b7c065e2", size = 403330, upload-time = "2026-03-12T14:53:40.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/26/448453925b6ce0c29d8b54327caa71ee4835511aef02070467402273079c/sentence_transformers-5.3.0.tar.gz", hash = "sha256:414a0a881f53a4df0e6cbace75f823bfcb6b94d674c42a384b498959b7c065e2", size = 403330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/9c/2fa7224058cad8df68d84bafee21716f30892cecc7ad1ad73bde61d23754/sentence_transformers-5.3.0-py3-none-any.whl", hash = "sha256:dca6b98db790274a68185d27a65801b58b4caf653a4e556b5f62827509347c7d", size = 512390, upload-time = "2026-03-12T14:53:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9c/2fa7224058cad8df68d84bafee21716f30892cecc7ad1ad73bde61d23754/sentence_transformers-5.3.0-py3-none-any.whl", hash = "sha256:dca6b98db790274a68185d27a65801b58b4caf653a4e556b5f62827509347c7d", size = 512390 }, ] [[package]] name = "setuptools" version = "81.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021 }, ] [[package]] @@ -7767,75 +7750,75 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489 } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, - { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, - { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644, upload-time = "2025-09-24T13:50:44.886Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887, upload-time = "2025-09-24T13:50:46.735Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931, upload-time = "2025-09-24T13:50:48.374Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855, upload-time = "2025-09-24T13:50:50.037Z" }, - { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960, upload-time = "2025-09-24T13:50:51.74Z" }, - { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851, upload-time = "2025-09-24T13:50:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890, upload-time = "2025-09-24T13:50:55.337Z" }, - { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151, upload-time = "2025-09-24T13:50:57.153Z" }, - { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130, upload-time = "2025-09-24T13:50:58.49Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802, upload-time = "2025-09-24T13:50:59.871Z" }, - { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460, upload-time = "2025-09-24T13:51:02.08Z" }, - { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223, upload-time = "2025-09-24T13:51:04.472Z" }, - { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760, upload-time = "2025-09-24T13:51:06.455Z" }, - { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078, upload-time = "2025-09-24T13:51:08.584Z" }, - { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178, upload-time = "2025-09-24T13:51:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756, upload-time = "2025-09-24T13:51:12.105Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290, upload-time = "2025-09-24T13:51:13.56Z" }, - { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463, upload-time = "2025-09-24T13:51:14.972Z" }, - { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145, upload-time = "2025-09-24T13:51:16.961Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806, upload-time = "2025-09-24T13:51:18.712Z" }, - { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803, upload-time = "2025-09-24T13:51:20.37Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301, upload-time = "2025-09-24T13:51:21.887Z" }, - { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247, upload-time = "2025-09-24T13:51:23.401Z" }, - { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019, upload-time = "2025-09-24T13:51:24.873Z" }, - { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137, upload-time = "2025-09-24T13:51:26.665Z" }, - { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884, upload-time = "2025-09-24T13:51:28.029Z" }, - { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320, upload-time = "2025-09-24T13:51:29.903Z" }, - { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931, upload-time = "2025-09-24T13:51:32.699Z" }, - { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406, upload-time = "2025-09-24T13:51:34.189Z" }, - { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511, upload-time = "2025-09-24T13:51:36.297Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550 }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556 }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308 }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844 }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714 }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745 }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861 }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887 }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931 }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855 }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960 }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851 }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890 }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151 }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130 }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802 }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460 }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223 }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760 }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078 }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178 }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756 }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290 }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463 }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145 }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806 }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301 }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247 }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019 }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137 }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884 }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320 }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931 }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406 }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511 }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682 }, ] [[package]] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 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" }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, ] [[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" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 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" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] [[package]] name = "slack-sdk" version = "3.41.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/35/fc009118a13187dd9731657c60138e5a7c2dea88681a7f04dc406af5da7d/slack_sdk-3.41.0.tar.gz", hash = "sha256:eb61eb12a65bebeca9cb5d36b3f799e836ed2be21b456d15df2627cfe34076ca", size = 250568, upload-time = "2026-03-12T16:10:11.381Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/35/fc009118a13187dd9731657c60138e5a7c2dea88681a7f04dc406af5da7d/slack_sdk-3.41.0.tar.gz", hash = "sha256:eb61eb12a65bebeca9cb5d36b3f799e836ed2be21b456d15df2627cfe34076ca", size = 250568 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/df/2e4be347ff98281b505cc0ccf141408cdd25eb5ca9f3830deb361b2472d3/slack_sdk-3.41.0-py2.py3-none-any.whl", hash = "sha256:bb18dcdfff1413ec448e759cf807ec3324090993d8ab9111c74081623b692a89", size = 313885, upload-time = "2026-03-12T16:10:09.811Z" }, + { url = "https://files.pythonhosted.org/packages/a1/df/2e4be347ff98281b505cc0ccf141408cdd25eb5ca9f3830deb361b2472d3/slack_sdk-3.41.0-py2.py3-none-any.whl", hash = "sha256:bb18dcdfff1413ec448e759cf807ec3324090993d8ab9111c74081623b692a89", size = 313885 }, ] [[package]] @@ -7845,9 +7828,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "limits" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028, upload-time = "2024-02-05T12:11:52.13Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670, upload-time = "2024-02-05T12:11:50.898Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670 }, ] [[package]] @@ -7857,18 +7840,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/be/a66598b305763861a9ab15ff0f2fbc44e47b1ce7a776797337a4eef37c66/smart_open-7.5.1.tar.gz", hash = "sha256:3f08e16827c4733699e6b2cc40328a3568f900cb12ad9a3ad233ba6c872d9fe7", size = 54034, upload-time = "2026-02-23T11:01:28.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/be/a66598b305763861a9ab15ff0f2fbc44e47b1ce7a776797337a4eef37c66/smart_open-7.5.1.tar.gz", hash = "sha256:3f08e16827c4733699e6b2cc40328a3568f900cb12ad9a3ad233ba6c872d9fe7", size = 54034 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/ea/dcdecd68acebb49d3fd560473a43499b1635076f7f1ae8641c060fe7ce74/smart_open-7.5.1-py3-none-any.whl", hash = "sha256:3e07cbbd9c8a908bcb8e25d48becf1a5cbb4886fa975e9f34c672ed171df2318", size = 64108, upload-time = "2026-02-23T11:01:27.429Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ea/dcdecd68acebb49d3fd560473a43499b1635076f7f1ae8641c060fe7ce74/smart_open-7.5.1-py3-none-any.whl", hash = "sha256:3e07cbbd9c8a908bcb8e25d48becf1a5cbb4886fa975e9f34c672ed171df2318", size = 64108 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 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" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] @@ -7879,24 +7862,24 @@ dependencies = [ { name = "cffi" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, - { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, - { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, - { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751 }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250 }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406 }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729 }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646 }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881 }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162 }, ] [[package]] name = "soupsieve" version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627 } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016 }, ] [[package]] @@ -7924,32 +7907,32 @@ dependencies = [ { name = "wasabi" }, { name = "weasel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/d7/1924f32272f50d2f13275f16d6f869fcec47b2b676b64f91fa206bd30ef4/spacy-3.8.13.tar.gz", hash = "sha256:eb7c03c2bb16593c34d4c91974118f0931c6e6969dbfe895b1a026c6714176cf", size = 1328016, upload-time = "2026-03-23T17:44:32.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/d7/1924f32272f50d2f13275f16d6f869fcec47b2b676b64f91fa206bd30ef4/spacy-3.8.13.tar.gz", hash = "sha256:eb7c03c2bb16593c34d4c91974118f0931c6e6969dbfe895b1a026c6714176cf", size = 1328016 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/e1/e8f6dc7fc00582b8dcbf40fac5bce6c0ff366cf6db212decdacfaf13e584/spacy-3.8.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dd8ab0deefe9d2c7dccce1be1e65660a32cfc3cc114d0aa222ff52ae11be58ba", size = 6218311, upload-time = "2026-03-23T17:42:51.938Z" }, - { url = "https://files.pythonhosted.org/packages/31/fe/517de9e25a6ec469738c9e886c15cd96649b338c6ae475b9b3e4c4f5e03d/spacy-3.8.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eab059bc668336d0034f3f69aed6b661b626c174e97cc1b52296d0d923c24405", size = 6033843, upload-time = "2026-03-23T17:42:54.133Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a8/9a33c69f772f5c32a57c9ef7e692293b558e7bad202264d5586ab087fd29/spacy-3.8.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96a8e141fd7ff749fa6693c9c72187f802d334bcadf48b6b9d6ca9bff315ca73", size = 32725183, upload-time = "2026-03-23T17:42:58.847Z" }, - { url = "https://files.pythonhosted.org/packages/ba/cf/dcc0ea61270cb23a46d3efc437fe760e7a9c3cabcfaa770591329d1430f2/spacy-3.8.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:32c16f5be8c8006197554da3647d83311fb89bb31bb417d38f0d1da585877b0d", size = 33205816, upload-time = "2026-03-23T17:43:03.717Z" }, - { url = "https://files.pythonhosted.org/packages/90/6b/ce94ba2a166add3376aa2c976cb1a34d6387e9dc61103cfbe9192675d0b6/spacy-3.8.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7402154eae9d2c34d097f3f7f2bf3ed4ce6ef9d44e3035af15ccc7f357d122cb", size = 32090348, upload-time = "2026-03-23T17:43:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4d/20/df5076a162bda36b04cdfa9cd544ac2be4b47aed957b3922e4c8f75dc25a/spacy-3.8.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:879f467578569db73b5811aa0f0b9a3b3fb81a125c2f7a433dd5626bcaca53f6", size = 32991911, upload-time = "2026-03-23T17:43:14.543Z" }, - { url = "https://files.pythonhosted.org/packages/41/17/316dfdf5f8d1dd33a205e4f5e7190b67db73802180f7d08c8b306eb44375/spacy-3.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:a0c849b5c9cee5a930a5e8a63d0b07e095d4e3d371af6a70c496efa1d0851257", size = 14226861, upload-time = "2026-03-23T17:43:18.122Z" }, - { url = "https://files.pythonhosted.org/packages/be/a8/a794d5bbf7bc2df6770df2b14cd6811420d0e10fe81830920bf0e891c19a/spacy-3.8.13-cp312-cp312-win_arm64.whl", hash = "sha256:5d7f660f64c7d09778600b27b881a367075fe792cb5cc6932737aeda71a9aa09", size = 13628855, upload-time = "2026-03-23T17:43:22.162Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2d/dc15440fa58c12bab25cbd368bbf3b754eb11d1d3cc37f3aee47caa63695/spacy-3.8.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6db8903cf3fd8b40db9dab669c1e823a8884100f223dc8ec63c3744ff505d5fd", size = 6202080, upload-time = "2026-03-23T17:43:25.044Z" }, - { url = "https://files.pythonhosted.org/packages/1b/27/6d723fa0c3c9872d7b8c3fb1c76edef65c1db2de441ee87d119128cb82f7/spacy-3.8.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c26258bfed2f3bf5563c5994ee752ea8662bbac70924d28fbea4d1c10df48fd0", size = 6015435, upload-time = "2026-03-23T17:43:28.281Z" }, - { url = "https://files.pythonhosted.org/packages/c0/36/d13f36204290e7753ce849106fb732a1a1ff6c1af0a200f2c2f493a56e60/spacy-3.8.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a5f02eab8e6e8e9e790b1e8da05b70b3b1d4b1fb10dc4f44203da96939fdb977", size = 32510675, upload-time = "2026-03-23T17:43:32.657Z" }, - { url = "https://files.pythonhosted.org/packages/19/37/f2a0c986bc2d59b18547d5ffaabf57fd9d88e129ad7df5ebc9ccdc91e643/spacy-3.8.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17b14f28d83fe85390f420f89760ae36d515f3e0a236f8266c46335549806e3a", size = 32841103, upload-time = "2026-03-23T17:43:38.139Z" }, - { url = "https://files.pythonhosted.org/packages/37/31/b4a86cc013e4ac3c3c290ecade748c4623709a78db8a368ee0e152931c0b/spacy-3.8.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10fffb387e6afeba582e01efd9b5e0c5431ac323af134f4a71a749255182f4c8", size = 31763223, upload-time = "2026-03-23T17:43:42.823Z" }, - { url = "https://files.pythonhosted.org/packages/4f/fa/b3a406bdc2636aaa315b8539b88f262e78ca391afc4083e3e9806953b24a/spacy-3.8.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:500f6eb9e4711e75fc07d517962dbfb553ba144ccf1b1e8ffc71b014c9ad91b2", size = 32717863, upload-time = "2026-03-23T17:43:47.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/97/11ed2a980e7225b0a1a4041cb1cba44f40bd83682a08e450dd6171f054e4/spacy-3.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:1e679a8c5dd86c564a1185d894b1b8e50b52fa81bee518120fc2f86349d1879c", size = 14220413, upload-time = "2026-03-23T17:43:51.922Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e2/06633e779486f62b3ec7ddeeb4accc10fb9a356b59f5bed3771dfb89931b/spacy-3.8.13-cp313-cp313-win_arm64.whl", hash = "sha256:c086bff909d73be892b4eb6883070dd3e328592b8933f0059a6569c8c7904928", size = 13619060, upload-time = "2026-03-23T17:43:55.449Z" }, - { url = "https://files.pythonhosted.org/packages/f2/02/bf2943f61a8bd21cca90e4fe19c4da8752c386f02a76e326b33199e09953/spacy-3.8.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:edcdd4f99e2711fc90c6ba3e8e07f7dc9753d111377ba7b7b5eb0d7259b0d211", size = 6209920, upload-time = "2026-03-23T17:43:58.441Z" }, - { url = "https://files.pythonhosted.org/packages/e7/87/fa5e4eb2ccb660d5042eb9144134dc6238c132ec812131bb0aca296bcdd9/spacy-3.8.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b8fd0188f78e032ac58f70a00748d591e244f3d4718e0ead2d9418b4148c744d", size = 6040696, upload-time = "2026-03-23T17:44:00.704Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f3/9132878e0c18d627adffc1d23129a3706385d4f0fbe2ea5839523519452c/spacy-3.8.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0dc2d058cb919102bf0634b1b41aff64b867d9bab36bd35979ab658b5b2e4c27", size = 32431416, upload-time = "2026-03-23T17:44:05.435Z" }, - { url = "https://files.pythonhosted.org/packages/80/73/396c5c3aaef5004d04abbda4ec736ce4d33944aff6722e974cebdd4023fd/spacy-3.8.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ef884df658d3b60d91cbc1e9b62a6d932589d2fa3f322c3f739b6ffdf3303e0c", size = 32483676, upload-time = "2026-03-23T17:44:10.913Z" }, - { url = "https://files.pythonhosted.org/packages/71/01/15ceb2097a817c2912297eac29f801bd71d895e93d7557110937c88f7c77/spacy-3.8.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:86f58d686d1ba6f0dc818005ee6ad87ade8dde7224012f1870a0d31abccd349e", size = 31732468, upload-time = "2026-03-23T17:44:15.967Z" }, - { url = "https://files.pythonhosted.org/packages/03/c4/b4c39083df6209414faec7d7471994316dc6fca68c4d2f1f8f099f25c2e3/spacy-3.8.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbcb411b9749a1cd5e4325953212b3984d8c0f60b4976943e77fb0c7d5027383", size = 32473870, upload-time = "2026-03-23T17:44:21.36Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5c/9b0fa420b01809c0170b3cf0bf509ec06b4da96214995360815615b8e8fa/spacy-3.8.13-cp314-cp314-win_amd64.whl", hash = "sha256:bf9b6879d70201acb73fc5f07d550ff488d3b5f15a959e9896717b2635c8e137", size = 14405303, upload-time = "2026-03-23T17:44:25.83Z" }, - { url = "https://files.pythonhosted.org/packages/fa/58/0001fd8124b62a2a9984278d793e3abfa1b70e969fc26a8668755178db84/spacy-3.8.13-cp314-cp314-win_arm64.whl", hash = "sha256:b2a402f229fcb5dba5454c346468757bd3a5215809e784b85d739ca84916f05b", size = 13834663, upload-time = "2026-03-23T17:44:29.43Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e1/e8f6dc7fc00582b8dcbf40fac5bce6c0ff366cf6db212decdacfaf13e584/spacy-3.8.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dd8ab0deefe9d2c7dccce1be1e65660a32cfc3cc114d0aa222ff52ae11be58ba", size = 6218311 }, + { url = "https://files.pythonhosted.org/packages/31/fe/517de9e25a6ec469738c9e886c15cd96649b338c6ae475b9b3e4c4f5e03d/spacy-3.8.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eab059bc668336d0034f3f69aed6b661b626c174e97cc1b52296d0d923c24405", size = 6033843 }, + { url = "https://files.pythonhosted.org/packages/2c/a8/9a33c69f772f5c32a57c9ef7e692293b558e7bad202264d5586ab087fd29/spacy-3.8.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96a8e141fd7ff749fa6693c9c72187f802d334bcadf48b6b9d6ca9bff315ca73", size = 32725183 }, + { url = "https://files.pythonhosted.org/packages/ba/cf/dcc0ea61270cb23a46d3efc437fe760e7a9c3cabcfaa770591329d1430f2/spacy-3.8.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:32c16f5be8c8006197554da3647d83311fb89bb31bb417d38f0d1da585877b0d", size = 33205816 }, + { url = "https://files.pythonhosted.org/packages/90/6b/ce94ba2a166add3376aa2c976cb1a34d6387e9dc61103cfbe9192675d0b6/spacy-3.8.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7402154eae9d2c34d097f3f7f2bf3ed4ce6ef9d44e3035af15ccc7f357d122cb", size = 32090348 }, + { url = "https://files.pythonhosted.org/packages/4d/20/df5076a162bda36b04cdfa9cd544ac2be4b47aed957b3922e4c8f75dc25a/spacy-3.8.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:879f467578569db73b5811aa0f0b9a3b3fb81a125c2f7a433dd5626bcaca53f6", size = 32991911 }, + { url = "https://files.pythonhosted.org/packages/41/17/316dfdf5f8d1dd33a205e4f5e7190b67db73802180f7d08c8b306eb44375/spacy-3.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:a0c849b5c9cee5a930a5e8a63d0b07e095d4e3d371af6a70c496efa1d0851257", size = 14226861 }, + { url = "https://files.pythonhosted.org/packages/be/a8/a794d5bbf7bc2df6770df2b14cd6811420d0e10fe81830920bf0e891c19a/spacy-3.8.13-cp312-cp312-win_arm64.whl", hash = "sha256:5d7f660f64c7d09778600b27b881a367075fe792cb5cc6932737aeda71a9aa09", size = 13628855 }, + { url = "https://files.pythonhosted.org/packages/a7/2d/dc15440fa58c12bab25cbd368bbf3b754eb11d1d3cc37f3aee47caa63695/spacy-3.8.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6db8903cf3fd8b40db9dab669c1e823a8884100f223dc8ec63c3744ff505d5fd", size = 6202080 }, + { url = "https://files.pythonhosted.org/packages/1b/27/6d723fa0c3c9872d7b8c3fb1c76edef65c1db2de441ee87d119128cb82f7/spacy-3.8.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c26258bfed2f3bf5563c5994ee752ea8662bbac70924d28fbea4d1c10df48fd0", size = 6015435 }, + { url = "https://files.pythonhosted.org/packages/c0/36/d13f36204290e7753ce849106fb732a1a1ff6c1af0a200f2c2f493a56e60/spacy-3.8.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a5f02eab8e6e8e9e790b1e8da05b70b3b1d4b1fb10dc4f44203da96939fdb977", size = 32510675 }, + { url = "https://files.pythonhosted.org/packages/19/37/f2a0c986bc2d59b18547d5ffaabf57fd9d88e129ad7df5ebc9ccdc91e643/spacy-3.8.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17b14f28d83fe85390f420f89760ae36d515f3e0a236f8266c46335549806e3a", size = 32841103 }, + { url = "https://files.pythonhosted.org/packages/37/31/b4a86cc013e4ac3c3c290ecade748c4623709a78db8a368ee0e152931c0b/spacy-3.8.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10fffb387e6afeba582e01efd9b5e0c5431ac323af134f4a71a749255182f4c8", size = 31763223 }, + { url = "https://files.pythonhosted.org/packages/4f/fa/b3a406bdc2636aaa315b8539b88f262e78ca391afc4083e3e9806953b24a/spacy-3.8.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:500f6eb9e4711e75fc07d517962dbfb553ba144ccf1b1e8ffc71b014c9ad91b2", size = 32717863 }, + { url = "https://files.pythonhosted.org/packages/d9/97/11ed2a980e7225b0a1a4041cb1cba44f40bd83682a08e450dd6171f054e4/spacy-3.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:1e679a8c5dd86c564a1185d894b1b8e50b52fa81bee518120fc2f86349d1879c", size = 14220413 }, + { url = "https://files.pythonhosted.org/packages/ca/e2/06633e779486f62b3ec7ddeeb4accc10fb9a356b59f5bed3771dfb89931b/spacy-3.8.13-cp313-cp313-win_arm64.whl", hash = "sha256:c086bff909d73be892b4eb6883070dd3e328592b8933f0059a6569c8c7904928", size = 13619060 }, + { url = "https://files.pythonhosted.org/packages/f2/02/bf2943f61a8bd21cca90e4fe19c4da8752c386f02a76e326b33199e09953/spacy-3.8.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:edcdd4f99e2711fc90c6ba3e8e07f7dc9753d111377ba7b7b5eb0d7259b0d211", size = 6209920 }, + { url = "https://files.pythonhosted.org/packages/e7/87/fa5e4eb2ccb660d5042eb9144134dc6238c132ec812131bb0aca296bcdd9/spacy-3.8.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b8fd0188f78e032ac58f70a00748d591e244f3d4718e0ead2d9418b4148c744d", size = 6040696 }, + { url = "https://files.pythonhosted.org/packages/ed/f3/9132878e0c18d627adffc1d23129a3706385d4f0fbe2ea5839523519452c/spacy-3.8.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0dc2d058cb919102bf0634b1b41aff64b867d9bab36bd35979ab658b5b2e4c27", size = 32431416 }, + { url = "https://files.pythonhosted.org/packages/80/73/396c5c3aaef5004d04abbda4ec736ce4d33944aff6722e974cebdd4023fd/spacy-3.8.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ef884df658d3b60d91cbc1e9b62a6d932589d2fa3f322c3f739b6ffdf3303e0c", size = 32483676 }, + { url = "https://files.pythonhosted.org/packages/71/01/15ceb2097a817c2912297eac29f801bd71d895e93d7557110937c88f7c77/spacy-3.8.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:86f58d686d1ba6f0dc818005ee6ad87ade8dde7224012f1870a0d31abccd349e", size = 31732468 }, + { url = "https://files.pythonhosted.org/packages/03/c4/b4c39083df6209414faec7d7471994316dc6fca68c4d2f1f8f099f25c2e3/spacy-3.8.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbcb411b9749a1cd5e4325953212b3984d8c0f60b4976943e77fb0c7d5027383", size = 32473870 }, + { url = "https://files.pythonhosted.org/packages/f2/5c/9b0fa420b01809c0170b3cf0bf509ec06b4da96214995360815615b8e8fa/spacy-3.8.13-cp314-cp314-win_amd64.whl", hash = "sha256:bf9b6879d70201acb73fc5f07d550ff488d3b5f15a959e9896717b2635c8e137", size = 14405303 }, + { url = "https://files.pythonhosted.org/packages/fa/58/0001fd8124b62a2a9984278d793e3abfa1b70e969fc26a8668755178db84/spacy-3.8.13-cp314-cp314-win_arm64.whl", hash = "sha256:b2a402f229fcb5dba5454c346468757bd3a5215809e784b85d739ca84916f05b", size = 13834663 }, ] [[package]] @@ -7961,27 +7944,27 @@ dependencies = [ { name = "curated-transformers" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/b3/a4fd3cf28008cbe1d95463b5c76a0d9c8da7b9ad4f06289c2be4aae62052/spacy_curated_transformers-0.3.1.tar.gz", hash = "sha256:7e53fccf64260e641b0a3f2b65b6d98381b86cef6eeb21ce279e8db849e8525d", size = 218990, upload-time = "2025-05-28T10:29:32.69Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/b3/a4fd3cf28008cbe1d95463b5c76a0d9c8da7b9ad4f06289c2be4aae62052/spacy_curated_transformers-0.3.1.tar.gz", hash = "sha256:7e53fccf64260e641b0a3f2b65b6d98381b86cef6eeb21ce279e8db849e8525d", size = 218990 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d8/f053d43125ae4ad14f3e2a12a475a656128233f1f40a272c6e09a05c73e8/spacy_curated_transformers-0.3.1-py2.py3-none-any.whl", hash = "sha256:503559b6a1d6e44ec2c978e18ed871ce5c3d56871dc9216c0e1523428204e610", size = 237943, upload-time = "2025-05-28T10:29:31.058Z" }, + { url = "https://files.pythonhosted.org/packages/42/d8/f053d43125ae4ad14f3e2a12a475a656128233f1f40a272c6e09a05c73e8/spacy_curated_transformers-0.3.1-py2.py3-none-any.whl", hash = "sha256:503559b6a1d6e44ec2c978e18ed871ce5c3d56871dc9216c0e1523428204e610", size = 237943 }, ] [[package]] name = "spacy-legacy" version = "3.0.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806, upload-time = "2023-01-23T09:04:15.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971, upload-time = "2023-01-23T09:04:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971 }, ] [[package]] name = "spacy-loggers" version = "1.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811, upload-time = "2023-09-11T12:26:52.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811 } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343, upload-time = "2023-09-11T12:26:50.586Z" }, + { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343 }, ] [[package]] @@ -7992,42 +7975,42 @@ 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/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, - { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, - { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, - { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, - { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, - { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737 }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020 }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983 }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690 }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738 }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546 }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484 }, + { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599 }, + { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825 }, + { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200 }, + { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876 }, + { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045 }, + { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700 }, + { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851 }, + { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525 }, + { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611 }, + { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812 }, + { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335 }, + { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095 }, + { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401 }, + { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528 }, + { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523 }, + { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312 }, + { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565 }, + { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205 }, + { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519 }, + { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611 }, + { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326 }, + { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453 }, + { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209 }, + { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198 }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202 }, ] [package.optional-dependencies] @@ -8042,40 +8025,40 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "catalogue" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/db/f794f219a6c788b881252d2536a8c4a97d2bdaadc690391e1cb53d123d71/srsly-2.5.3.tar.gz", hash = "sha256:08f98dbecbff3a31466c4ae7c833131f59d3655a0ad8ac749e6e2c149e2b0680", size = 490881, upload-time = "2026-03-23T11:56:59.865Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/db/f794f219a6c788b881252d2536a8c4a97d2bdaadc690391e1cb53d123d71/srsly-2.5.3.tar.gz", hash = "sha256:08f98dbecbff3a31466c4ae7c833131f59d3655a0ad8ac749e6e2c149e2b0680", size = 490881 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/e9f7fcec4cc92ad8bad6316c4241638b8cf7380382d4489d94ec6c436452/srsly-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:71e51c046ccbeefb86524c6b1e17574f579c6ac4dc8ea4a09437d3e8f88342d3", size = 658379, upload-time = "2026-03-23T11:55:59.85Z" }, - { url = "https://files.pythonhosted.org/packages/21/e4/fea4512e9785f58509b2cf67d993323848e583161b5fcfdc7dd9d7c1f3df/srsly-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f73c0db911552e94fe2016e1759d261d2f47926f68826664cada3723c87006a", size = 658513, upload-time = "2026-03-23T11:56:01.239Z" }, - { url = "https://files.pythonhosted.org/packages/20/b1/53591681b6ff2699a4f97b2d5552ba196eaa6a979b0873605f4c04b5f7ee/srsly-2.5.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c1ac27ae5f4bb9163c7d2c45fc8ec173aac3d92e32086d9472b326c5c6e570e", size = 1172265, upload-time = "2026-03-23T11:56:02.589Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c9/741e29f534919a944a16da4184924b1d3404c4bf60716ab2b91be771d1e3/srsly-2.5.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99026bcd9cbd3211cc36517400b04ca0fc5d3e412b14daf84ee6e65f67d9a2d8", size = 1180873, upload-time = "2026-03-23T11:56:03.944Z" }, - { url = "https://files.pythonhosted.org/packages/89/57/5554f786eccf78b2750d6ac63be126e1b67badec2cb409dd611cf6f8c52b/srsly-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07d682679e639eb46ff7e6da4a92714f4d5ffe351d088ee66f221e9b1f8865bb", size = 1120437, upload-time = "2026-03-23T11:56:05.283Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/9b4f73b1be3692f86d72ccc131c8e50f26f824d5c8830a59390bcc5b60ef/srsly-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e0542d85d6b55cf2934050d6ffcb1cd76c768dcf9572e7467002cf087bb366d", size = 1137376, upload-time = "2026-03-23T11:56:06.613Z" }, - { url = "https://files.pythonhosted.org/packages/5a/de/89ca640ca1953c4612279ce515d0af35658df3c06cdb324329bc91b4a7e1/srsly-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:598f1e494c18cacb978299d77125415a586417081959f8ec3f068b32d97f8933", size = 652459, upload-time = "2026-03-23T11:56:07.994Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4f/7ab6d49e36d9cc72ee15746cabd116eb6f338be8a06c1882968ee9d6c7d7/srsly-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:4b1b721cd3ad1a9b2343519aadc786a4d09d5c0666962d49852eb12d6ec3fe26", size = 638411, upload-time = "2026-03-23T11:56:09.31Z" }, - { url = "https://files.pythonhosted.org/packages/9d/5c/12901e3794f4158abc6da750725aad6c2afddb1e4227b300fe7c71f66957/srsly-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e67b6bbacbfadea5e100266d2797f2d4cec9883ea4dc84a5537673850036a8d8", size = 656750, upload-time = "2026-03-23T11:56:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/04/61/181c26370995f96f56f1b64b801e3ca1e0d703fc36506ae28606d62369fb/srsly-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:348c231b4477d8fe86603131d0f166d2feac9c372704dfc4398be71cc5b6fb07", size = 656746, upload-time = "2026-03-23T11:56:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/77/c6/35876c78889f8ffe11ed3521644e666c3aef20ea31527b70f47456cf35c2/srsly-2.5.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b0938c2978c91ae1ef9c1f2ba35abb86330e198fb23469e356eba311e02233ee", size = 1155762, upload-time = "2026-03-23T11:56:14.075Z" }, - { url = "https://files.pythonhosted.org/packages/3e/da/40b71ca9906c8eb8f8feb6ac11d33dad458c85a56e1de764b96d402168a0/srsly-2.5.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f6a837954429ecbe6dcdd27390d2fb4c7d01a3f99c9ffcf9ce66b2a6dd1b738", size = 1161092, upload-time = "2026-03-23T11:56:15.778Z" }, - { url = "https://files.pythonhosted.org/packages/dc/14/c0dd30cc8b93ce8137ff4766f743c882440ce49195fffc5d50eaeef311a6/srsly-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3576c125c486ce2958c2047e8858fe3cfc9ea877adfa05203b0986f9badee355", size = 1109984, upload-time = "2026-03-23T11:56:17.056Z" }, - { url = "https://files.pythonhosted.org/packages/08/f3/34354f183d8faafc631585571224b54d1b4b67e796972c36519c074ca355/srsly-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fb59c42922e095d1ea36085c55bc16e2adb06a7bfe57b24d381e0194ae699f2", size = 1128409, upload-time = "2026-03-23T11:56:18.761Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d9/5531f8a19492060b4e76e4ab06aca6f096fb5128fe18cc813d1772daf653/srsly-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:111805927f05f5db440aeeacb85ce43da0b19ce7b2a09567a9ef8d30f3cc4d83", size = 650820, upload-time = "2026-03-23T11:56:20.096Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8a/62fb7a971eca29e12f03fb9ddacb058548c14d33e5b5675ff0f85839cc7b/srsly-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:0f106b0a700ab56e4a7c431b0f1444009ab6cb332edc7bbf6811c2a43f4722cb", size = 637278, upload-time = "2026-03-23T11:56:21.439Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5b/e4ef43c2a381711230af98d4c94a5323df48d6a7899ee652e05bf889290e/srsly-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:39c13d552a9f9674a12cdcdc66b0c2f02f3430d0cd04c5f9cf598824c2bd3d65", size = 661294, upload-time = "2026-03-23T11:56:23.29Z" }, - { url = "https://files.pythonhosted.org/packages/92/2d/ebce7f3717e52cd0a01f4ec570f388f3b7098526794fcf1ad734e0b8f852/srsly-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14c930767cc169611a2dc14e23bc7638cfb616d6f79029700ade033607343540", size = 660952, upload-time = "2026-03-23T11:56:24.908Z" }, - { url = "https://files.pythonhosted.org/packages/22/47/a8f3e9b214be2624c8e8a78d38ca7b1d4e26b92d57018412e4bfc4abe89a/srsly-2.5.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f2d464f0d0237e32fb53f0ec6f05418652c550e772b50e9918e83a1577cba4d", size = 1154554, upload-time = "2026-03-23T11:56:26.608Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/2a89dc3180a51e633a87a079ca064225f4aaf46c7b2a5fc720e28f261d98/srsly-2.5.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d18933248a5bb0ad56a1bae6003a9a7f37daac2ecb0c5bcbfaaf081b317e1c84", size = 1155746, upload-time = "2026-03-23T11:56:28.102Z" }, - { url = "https://files.pythonhosted.org/packages/b8/36/72e5ce3153927ca404b6f5bf5280e6ff3399c11557df472b153945468e0a/srsly-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7ea5412ea229e571ac9738cbe14f845cc06c8e4e956afb5f42061ccd087ef31f", size = 1112374, upload-time = "2026-03-23T11:56:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/04/b2/0895de109c28eca0d41a811ab7c076d4e4a505e8466f06bae22f5180a1dd/srsly-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8d3988970b4cf7d03bdd5b5169302ff84562dd2e1e0f84aeb34df3e5b5dc19bf", size = 1127732, upload-time = "2026-03-23T11:56:31.458Z" }, - { url = "https://files.pythonhosted.org/packages/c7/79/a37fa7759797fbdfe0a2e029ab13e78b1e81e191220d2bb8ff57d869aefb/srsly-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:6a02d7dcc16126c8fae1c1c09b2072798a1dc482ab5f9c52b12c7114dac47325", size = 656467, upload-time = "2026-03-23T11:56:33.14Z" }, - { url = "https://files.pythonhosted.org/packages/d7/25/0dae019b3b90ad9037f91de4c390555cdaac9460a93ad62b02b03babdff5/srsly-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:1c9129c4abe31903ff7996904a51afdd5428060de6c3d12af49a4da5e8df2821", size = 643040, upload-time = "2026-03-23T11:56:34.448Z" }, - { url = "https://files.pythonhosted.org/packages/3a/44/72dd5285b2e05435d98b0797f101d91d9b345d491ddc1fdb9bd09e27ccb8/srsly-2.5.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:29d5d01ba4c2e9c01f936e5e6d5babc4a47b38c9cbd6e1ec23f6d5a49df32605", size = 666200, upload-time = "2026-03-23T11:56:35.753Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ad/002c71b87fc3f648c9bf0ec47de0c3822bf2c95c8896a589dd03e7fd3977/srsly-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c8df4039426d99f0148b5743542842ab96b82daded0b342555e15a639927757", size = 667409, upload-time = "2026-03-23T11:56:37.172Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/2cea3d5e80aeecfc4ece9e7e1783e7792cc3bad7ab85ab585882e1db4e38/srsly-2.5.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:06a43d63bde2e8cccadb953d7fff70b18196ca286b65dd2ad16006d65f3f8166", size = 1265941, upload-time = "2026-03-23T11:56:38.825Z" }, - { url = "https://files.pythonhosted.org/packages/aa/38/8a4d7e86dd0370a2e5af251b646000197bb5b7e0f9aa360c71bbfb253d0d/srsly-2.5.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:808cfafc047f0dec507a34c8fa8e4cda5722737fd33577df73452f52f7aca644", size = 1250693, upload-time = "2026-03-23T11:56:40.449Z" }, - { url = "https://files.pythonhosted.org/packages/99/05/340129de5ea7b237271b12f8a6962cfa7eb0c5a3056794626d348c5ae7c7/srsly-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:71d4cbe2b2a1335c76ed0acae2dc862163787d8b01a705e1949796907ed94ccd", size = 1242408, upload-time = "2026-03-23T11:56:41.8Z" }, - { url = "https://files.pythonhosted.org/packages/01/cb/d7fee7ab27c6aa2e3f865fb7b50ba18c81a4c763bba12bdf53df246441bc/srsly-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f69083d33cb329cfc74317da937fb3270c0f40fabc1b4488702d8074b4a3e", size = 1242749, upload-time = "2026-03-23T11:56:43.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d1/9bad3a0f2fa7b72f4e0cf1d267b00513092d20ef538c47f72823ae4f7656/srsly-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:8ac016ffaeac35bc010992b71bf8afdd39d458f201c8138d84cf78778a936e6c", size = 673783, upload-time = "2026-03-23T11:56:44.875Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ae/57d1d7af907e20c077e113e0e4976f87b82c0a415403d99284a262229dd0/srsly-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d822083fe26ec6728bd8c273ac121fc4ab3864a0fdf0cf0ff3efb188fcd209ed", size = 650229, upload-time = "2026-03-23T11:56:46.148Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/e9f7fcec4cc92ad8bad6316c4241638b8cf7380382d4489d94ec6c436452/srsly-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:71e51c046ccbeefb86524c6b1e17574f579c6ac4dc8ea4a09437d3e8f88342d3", size = 658379 }, + { url = "https://files.pythonhosted.org/packages/21/e4/fea4512e9785f58509b2cf67d993323848e583161b5fcfdc7dd9d7c1f3df/srsly-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f73c0db911552e94fe2016e1759d261d2f47926f68826664cada3723c87006a", size = 658513 }, + { url = "https://files.pythonhosted.org/packages/20/b1/53591681b6ff2699a4f97b2d5552ba196eaa6a979b0873605f4c04b5f7ee/srsly-2.5.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c1ac27ae5f4bb9163c7d2c45fc8ec173aac3d92e32086d9472b326c5c6e570e", size = 1172265 }, + { url = "https://files.pythonhosted.org/packages/4e/c9/741e29f534919a944a16da4184924b1d3404c4bf60716ab2b91be771d1e3/srsly-2.5.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99026bcd9cbd3211cc36517400b04ca0fc5d3e412b14daf84ee6e65f67d9a2d8", size = 1180873 }, + { url = "https://files.pythonhosted.org/packages/89/57/5554f786eccf78b2750d6ac63be126e1b67badec2cb409dd611cf6f8c52b/srsly-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07d682679e639eb46ff7e6da4a92714f4d5ffe351d088ee66f221e9b1f8865bb", size = 1120437 }, + { url = "https://files.pythonhosted.org/packages/eb/95/9b4f73b1be3692f86d72ccc131c8e50f26f824d5c8830a59390bcc5b60ef/srsly-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e0542d85d6b55cf2934050d6ffcb1cd76c768dcf9572e7467002cf087bb366d", size = 1137376 }, + { url = "https://files.pythonhosted.org/packages/5a/de/89ca640ca1953c4612279ce515d0af35658df3c06cdb324329bc91b4a7e1/srsly-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:598f1e494c18cacb978299d77125415a586417081959f8ec3f068b32d97f8933", size = 652459 }, + { url = "https://files.pythonhosted.org/packages/6d/4f/7ab6d49e36d9cc72ee15746cabd116eb6f338be8a06c1882968ee9d6c7d7/srsly-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:4b1b721cd3ad1a9b2343519aadc786a4d09d5c0666962d49852eb12d6ec3fe26", size = 638411 }, + { url = "https://files.pythonhosted.org/packages/9d/5c/12901e3794f4158abc6da750725aad6c2afddb1e4227b300fe7c71f66957/srsly-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e67b6bbacbfadea5e100266d2797f2d4cec9883ea4dc84a5537673850036a8d8", size = 656750 }, + { url = "https://files.pythonhosted.org/packages/04/61/181c26370995f96f56f1b64b801e3ca1e0d703fc36506ae28606d62369fb/srsly-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:348c231b4477d8fe86603131d0f166d2feac9c372704dfc4398be71cc5b6fb07", size = 656746 }, + { url = "https://files.pythonhosted.org/packages/77/c6/35876c78889f8ffe11ed3521644e666c3aef20ea31527b70f47456cf35c2/srsly-2.5.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b0938c2978c91ae1ef9c1f2ba35abb86330e198fb23469e356eba311e02233ee", size = 1155762 }, + { url = "https://files.pythonhosted.org/packages/3e/da/40b71ca9906c8eb8f8feb6ac11d33dad458c85a56e1de764b96d402168a0/srsly-2.5.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f6a837954429ecbe6dcdd27390d2fb4c7d01a3f99c9ffcf9ce66b2a6dd1b738", size = 1161092 }, + { url = "https://files.pythonhosted.org/packages/dc/14/c0dd30cc8b93ce8137ff4766f743c882440ce49195fffc5d50eaeef311a6/srsly-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3576c125c486ce2958c2047e8858fe3cfc9ea877adfa05203b0986f9badee355", size = 1109984 }, + { url = "https://files.pythonhosted.org/packages/08/f3/34354f183d8faafc631585571224b54d1b4b67e796972c36519c074ca355/srsly-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fb59c42922e095d1ea36085c55bc16e2adb06a7bfe57b24d381e0194ae699f2", size = 1128409 }, + { url = "https://files.pythonhosted.org/packages/a4/d9/5531f8a19492060b4e76e4ab06aca6f096fb5128fe18cc813d1772daf653/srsly-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:111805927f05f5db440aeeacb85ce43da0b19ce7b2a09567a9ef8d30f3cc4d83", size = 650820 }, + { url = "https://files.pythonhosted.org/packages/8e/8a/62fb7a971eca29e12f03fb9ddacb058548c14d33e5b5675ff0f85839cc7b/srsly-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:0f106b0a700ab56e4a7c431b0f1444009ab6cb332edc7bbf6811c2a43f4722cb", size = 637278 }, + { url = "https://files.pythonhosted.org/packages/e1/5b/e4ef43c2a381711230af98d4c94a5323df48d6a7899ee652e05bf889290e/srsly-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:39c13d552a9f9674a12cdcdc66b0c2f02f3430d0cd04c5f9cf598824c2bd3d65", size = 661294 }, + { url = "https://files.pythonhosted.org/packages/92/2d/ebce7f3717e52cd0a01f4ec570f388f3b7098526794fcf1ad734e0b8f852/srsly-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14c930767cc169611a2dc14e23bc7638cfb616d6f79029700ade033607343540", size = 660952 }, + { url = "https://files.pythonhosted.org/packages/22/47/a8f3e9b214be2624c8e8a78d38ca7b1d4e26b92d57018412e4bfc4abe89a/srsly-2.5.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f2d464f0d0237e32fb53f0ec6f05418652c550e772b50e9918e83a1577cba4d", size = 1154554 }, + { url = "https://files.pythonhosted.org/packages/d6/71/2a89dc3180a51e633a87a079ca064225f4aaf46c7b2a5fc720e28f261d98/srsly-2.5.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d18933248a5bb0ad56a1bae6003a9a7f37daac2ecb0c5bcbfaaf081b317e1c84", size = 1155746 }, + { url = "https://files.pythonhosted.org/packages/b8/36/72e5ce3153927ca404b6f5bf5280e6ff3399c11557df472b153945468e0a/srsly-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7ea5412ea229e571ac9738cbe14f845cc06c8e4e956afb5f42061ccd087ef31f", size = 1112374 }, + { url = "https://files.pythonhosted.org/packages/04/b2/0895de109c28eca0d41a811ab7c076d4e4a505e8466f06bae22f5180a1dd/srsly-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8d3988970b4cf7d03bdd5b5169302ff84562dd2e1e0f84aeb34df3e5b5dc19bf", size = 1127732 }, + { url = "https://files.pythonhosted.org/packages/c7/79/a37fa7759797fbdfe0a2e029ab13e78b1e81e191220d2bb8ff57d869aefb/srsly-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:6a02d7dcc16126c8fae1c1c09b2072798a1dc482ab5f9c52b12c7114dac47325", size = 656467 }, + { url = "https://files.pythonhosted.org/packages/d7/25/0dae019b3b90ad9037f91de4c390555cdaac9460a93ad62b02b03babdff5/srsly-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:1c9129c4abe31903ff7996904a51afdd5428060de6c3d12af49a4da5e8df2821", size = 643040 }, + { url = "https://files.pythonhosted.org/packages/3a/44/72dd5285b2e05435d98b0797f101d91d9b345d491ddc1fdb9bd09e27ccb8/srsly-2.5.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:29d5d01ba4c2e9c01f936e5e6d5babc4a47b38c9cbd6e1ec23f6d5a49df32605", size = 666200 }, + { url = "https://files.pythonhosted.org/packages/d2/ad/002c71b87fc3f648c9bf0ec47de0c3822bf2c95c8896a589dd03e7fd3977/srsly-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c8df4039426d99f0148b5743542842ab96b82daded0b342555e15a639927757", size = 667409 }, + { url = "https://files.pythonhosted.org/packages/2a/35/2cea3d5e80aeecfc4ece9e7e1783e7792cc3bad7ab85ab585882e1db4e38/srsly-2.5.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:06a43d63bde2e8cccadb953d7fff70b18196ca286b65dd2ad16006d65f3f8166", size = 1265941 }, + { url = "https://files.pythonhosted.org/packages/aa/38/8a4d7e86dd0370a2e5af251b646000197bb5b7e0f9aa360c71bbfb253d0d/srsly-2.5.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:808cfafc047f0dec507a34c8fa8e4cda5722737fd33577df73452f52f7aca644", size = 1250693 }, + { url = "https://files.pythonhosted.org/packages/99/05/340129de5ea7b237271b12f8a6962cfa7eb0c5a3056794626d348c5ae7c7/srsly-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:71d4cbe2b2a1335c76ed0acae2dc862163787d8b01a705e1949796907ed94ccd", size = 1242408 }, + { url = "https://files.pythonhosted.org/packages/01/cb/d7fee7ab27c6aa2e3f865fb7b50ba18c81a4c763bba12bdf53df246441bc/srsly-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f69083d33cb329cfc74317da937fb3270c0f40fabc1b4488702d8074b4a3e", size = 1242749 }, + { url = "https://files.pythonhosted.org/packages/d8/d1/9bad3a0f2fa7b72f4e0cf1d267b00513092d20ef538c47f72823ae4f7656/srsly-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:8ac016ffaeac35bc010992b71bf8afdd39d458f201c8138d84cf78778a936e6c", size = 673783 }, + { url = "https://files.pythonhosted.org/packages/2a/ae/57d1d7af907e20c077e113e0e4976f87b82c0a415403d99284a262229dd0/srsly-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d822083fe26ec6728bd8c273ac121fc4ab3864a0fdf0cf0ff3efb188fcd209ed", size = 650229 }, ] [[package]] @@ -8086,9 +8069,9 @@ dependencies = [ { name = "anyio" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/08/8f554b0e5bad3e4e880521a1686d96c05198471eed860b0eb89b57ea3636/sse_starlette-3.1.1.tar.gz", hash = "sha256:bffa531420c1793ab224f63648c059bcadc412bf9fdb1301ac8de1cf9a67b7fb", size = 24306, upload-time = "2025-12-26T15:22:53.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/08/8f554b0e5bad3e4e880521a1686d96c05198471eed860b0eb89b57ea3636/sse_starlette-3.1.1.tar.gz", hash = "sha256:bffa531420c1793ab224f63648c059bcadc412bf9fdb1301ac8de1cf9a67b7fb", size = 24306 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/31/4c281581a0f8de137b710a07f65518b34bcf333b201cfa06cfda9af05f8a/sse_starlette-3.1.1-py3-none-any.whl", hash = "sha256:bb38f71ae74cfd86b529907a9fda5632195dfa6ae120f214ea4c890c7ee9d436", size = 12442, upload-time = "2025-12-26T15:22:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/e3/31/4c281581a0f8de137b710a07f65518b34bcf333b201cfa06cfda9af05f8a/sse_starlette-3.1.1-py3-none-any.whl", hash = "sha256:bb38f71ae74cfd86b529907a9fda5632195dfa6ae120f214ea4c890c7ee9d436", size = 12442 }, ] [[package]] @@ -8099,9 +8082,9 @@ dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033 }, ] [[package]] @@ -8115,7 +8098,7 @@ dependencies = [ { name = "twine" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/d3/c238124fbf2dbe5eda203f0a1c4cd6c210e27993ed9780c4c1bf2ab0efbe/static_ffmpeg-3.0-py3-none-any.whl", hash = "sha256:79d9067264cefbb05e6b847be7d6cb7410b7b25adce40178a787f0137567c89f", size = 7927, upload-time = "2026-01-16T21:17:10.643Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/c238124fbf2dbe5eda203f0a1c4cd6c210e27993ed9780c4c1bf2ab0efbe/static_ffmpeg-3.0-py3-none-any.whl", hash = "sha256:79d9067264cefbb05e6b847be7d6cb7410b7b25adce40178a787f0137567c89f", size = 7927 }, ] [[package]] @@ -8126,14 +8109,14 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/5a/0cdea4b7911b8012936c765544109da27c0728f6911ec7aefe9d59e7a4f9/stripe-15.0.0.tar.gz", hash = "sha256:0717cd9ba8e8193cef8b1c488ce27836754df496ab6fb75864096e0cdf15e52d", size = 1486873, upload-time = "2026-03-26T01:39:04.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/5a/0cdea4b7911b8012936c765544109da27c0728f6911ec7aefe9d59e7a4f9/stripe-15.0.0.tar.gz", hash = "sha256:0717cd9ba8e8193cef8b1c488ce27836754df496ab6fb75864096e0cdf15e52d", size = 1486873 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/4a/4431c998c451cf07f8b4fed98f425b4aaf3d59cc4fb1e6f54d7713606688/stripe-15.0.0-py3-none-any.whl", hash = "sha256:434ec5267a7402a30b76786d159c18d0e138f89195969d6c56bea2e08d353be0", size = 2125454, upload-time = "2026-03-26T01:39:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/03/4a/4431c998c451cf07f8b4fed98f425b4aaf3d59cc4fb1e6f54d7713606688/stripe-15.0.0-py3-none-any.whl", hash = "sha256:434ec5267a7402a30b76786d159c18d0e138f89195969d6c56bea2e08d353be0", size = 2125454 }, ] [[package]] name = "surf-new-backend" -version = "0.0.25" +version = "0.0.26" source = { editable = "." } dependencies = [ { name = "alembic" }, @@ -8335,18 +8318,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, ] [[package]] name = "tabulate" version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814 }, ] [[package]] @@ -8358,18 +8341,18 @@ dependencies = [ { name = "requests" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/d1/197419d6133643848514e5e84e8f41886e825b73bf91ae235a1595c964f5/tavily_python-0.7.23.tar.gz", hash = "sha256:3b92232e0e29ab68898b765f281bb4f2c650b02210b64affbc48e15292e96161", size = 25968, upload-time = "2026-03-09T19:17:32.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/d1/197419d6133643848514e5e84e8f41886e825b73bf91ae235a1595c964f5/tavily_python-0.7.23.tar.gz", hash = "sha256:3b92232e0e29ab68898b765f281bb4f2c650b02210b64affbc48e15292e96161", size = 25968 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/27/f9c6e9249367be0772fb754849e03cbbc6ad8d80a479bf30ea8811828b2e/tavily_python-0.7.23-py3-none-any.whl", hash = "sha256:52ef85c44b926bce3f257570cd32bc1bd4db54666acf3105617f27411a59e188", size = 19079, upload-time = "2026-03-09T19:17:29.593Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/f9c6e9249367be0772fb754849e03cbbc6ad8d80a479bf30ea8811828b2e/tavily_python-0.7.23-py3-none-any.whl", hash = "sha256:52ef85c44b926bce3f257570cd32bc1bd4db54666acf3105617f27411a59e188", size = 19079 }, ] [[package]] name = "tenacity" version = "9.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926 }, ] [[package]] @@ -8390,41 +8373,41 @@ dependencies = [ { name = "srsly" }, { name = "wasabi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/46/76df95f2c327f9a9cef30c1523bf285627897097163584dcf5f77b2ebce2/thinc-8.3.13.tar.gz", hash = "sha256:68e658549fc1eb3ff92aed5147fcbb9c15d6e9cc0e623b4d0998d16522ffb4f9", size = 194640, upload-time = "2026-03-23T07:22:36.41Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/46/76df95f2c327f9a9cef30c1523bf285627897097163584dcf5f77b2ebce2/thinc-8.3.13.tar.gz", hash = "sha256:68e658549fc1eb3ff92aed5147fcbb9c15d6e9cc0e623b4d0998d16522ffb4f9", size = 194640 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/af/f7c1ebfe92eb5d27d7f2f3da67a11e2eb57bc30ab1553279af6dc65b65a8/thinc-8.3.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:77a41f66285321d20aaedaea1e87d7cd48dca6d2427bed1867ec7cba7109fc8d", size = 821097, upload-time = "2026-03-23T07:21:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/45/8f/69d7338575d98df85d0b54c0f5fc277dba72587fe9ab846ecdd12a998bcb/thinc-8.3.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3710d318b4e5460cf366a6f7b5ddbefb5d39dbd4cfa408222750fdc6c27c4411", size = 791932, upload-time = "2026-03-23T07:21:58.38Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a5/21d010c81e81e1589e5ccb4950e521804d13726e541e87f644c51815673b/thinc-8.3.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a08c87143a6d20177652dca1ec0dc815d88216d8fc62594a57e8bc45bf5ed49", size = 3854219, upload-time = "2026-03-23T07:21:59.819Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ff/6914bf370bd1d604d89e6dfb46b97d10cd9b00d42ff8c036283e92314a8c/thinc-8.3.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b5ec9ff313819e7d8667794a3559463fa89ff45aaa73e3fd8d6273b1e0d7a7f", size = 3903307, upload-time = "2026-03-23T07:22:01.652Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/5572b47fa155fb3388c071515b74024fa17a6efd1df9406da378f0aa84ef/thinc-8.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c9a48f2bc1e04f138240ed5f9b815a9141a5de26accd0f08fa0137fcefed258", size = 4836882, upload-time = "2026-03-23T07:22:03.565Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f0/a8d77c7bac089697c6df302cc3c936a1ab36a4720deae889e6f1dbcbd0eb/thinc-8.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79a29a44d76bd02f5ac0624268c6e42b3576ae472c791a8ae9c2d813ae789b59", size = 5033398, upload-time = "2026-03-23T07:22:05.045Z" }, - { url = "https://files.pythonhosted.org/packages/21/82/5651bb1f904d04220fc7670035ada921bf0638e2cff6444d67c12887a968/thinc-8.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:ed1dc709ac4f2f03b710457889e4e02f05de51bc8456980c241d0b28798bc7cb", size = 1721248, upload-time = "2026-03-23T07:22:06.749Z" }, - { url = "https://files.pythonhosted.org/packages/94/8d/683703de021ffbe46833d722b70f49ffbbca8e5bd6876256977555d92d7d/thinc-8.3.13-cp312-cp312-win_arm64.whl", hash = "sha256:c6a049703a6011c8fe26ee41af7e70272145594140d82f79bb23de619c6a6525", size = 1645777, upload-time = "2026-03-23T07:22:08.104Z" }, - { url = "https://files.pythonhosted.org/packages/af/b9/7b46942176df459d1804a9e77b0976f7c56f3abf3ec7485d0e5f836a0382/thinc-8.3.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2811dfd8d46d8b5d3b39051b23e64006b2994a5143b1978b436938018792af8", size = 817337, upload-time = "2026-03-23T07:22:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/a7/79/53085a72cd8f4fc4e6e313d05ea5aa98e870684f4a0fb318a9875fc0a964/thinc-8.3.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5593e6300cb1ebe0c0e546e9c9fb49e7c2627a0aa688795cd4f995a8b820d2ec", size = 788120, upload-time = "2026-03-23T07:22:11.215Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3e/d61b462b16da95ac6885f95bb395e672040ee594833e571a6edcffd234f5/thinc-8.3.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f697174d3fb474966ce50b430bbafa101a6d2f7ffb559dac4b5c59389ef72d22", size = 3844666, upload-time = "2026-03-23T07:22:12.67Z" }, - { url = "https://files.pythonhosted.org/packages/78/4c/898cc654bb123734c71ec5a425c02ca34439517d01ce1c95a6563295580e/thinc-8.3.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9c7c5c104737b414c8c4ec578e67d78b6c859afe25cbc0684402e721415bd7f", size = 3890658, upload-time = "2026-03-23T07:22:14.668Z" }, - { url = "https://files.pythonhosted.org/packages/cd/56/1abdbf0a4ad628e8a05d6516fe0745969649d805367a3dccad8ee872981b/thinc-8.3.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a99d0e242d1ccd23f9ae6bea7cd502f8626efa65c156b91d84581d0356696c3", size = 4819933, upload-time = "2026-03-23T07:22:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/f1/22/b84dbdc6be5055bbdb2a7352e2c393f67e8593c137f1b83c82bf1e062b6e/thinc-8.3.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e676edd21a747afbe3e6b9f3fca8b962e36d146ded03b070cb0c28e2dfbe9499", size = 5018099, upload-time = "2026-03-23T07:22:18.356Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a8/763cd7ba949334c9d2cddc92dadb68b344cb9546dc01b8d4a733dcaa16c1/thinc-8.3.13-cp313-cp313-win_amd64.whl", hash = "sha256:8ad40307f20e83f77af28ff5c6be0b86af7a8b251d1231c545508d2763157d8f", size = 1720309, upload-time = "2026-03-23T07:22:19.81Z" }, - { url = "https://files.pythonhosted.org/packages/f5/15/a11f7bb3cbc97dfecf32a90552f5a8f8a5c99316a99c6c17bdabf5baf256/thinc-8.3.13-cp313-cp313-win_arm64.whl", hash = "sha256:723949cab11d1925c15447928513a718276316cec6e0de28337cca0a62be0521", size = 1644606, upload-time = "2026-03-23T07:22:21.339Z" }, - { url = "https://files.pythonhosted.org/packages/80/40/f4937d113912c6d669ffe982356ab29dcb6c7fe3be926a15981dbbb6a91c/thinc-8.3.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7badb0be4825535e6362c19e8a41872b65409e9da46d3453a391b843a0720865", size = 817024, upload-time = "2026-03-23T07:22:23.005Z" }, - { url = "https://files.pythonhosted.org/packages/d2/00/4d4ed1a11ba2920b85a03a0683b16d97dc5beb2e78078dbf0e13e43bcea7/thinc-8.3.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:565300b7e13de799e5abff00d445f537e9256cf7da4dcb0d0f005fc16748a29e", size = 792096, upload-time = "2026-03-23T07:22:24.349Z" }, - { url = "https://files.pythonhosted.org/packages/44/5d/dc33d6932be8721af2ef76b4a3a6e8020648630eabae61fb916d2a861d1d/thinc-8.3.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c17cef1900a1aba7e1487493d16b8aa0a8633116f1b2a51c6649a4000697f17b", size = 3842215, upload-time = "2026-03-23T07:22:25.836Z" }, - { url = "https://files.pythonhosted.org/packages/af/bc/a6d37d8dadc2c5b524f51192413481160c42c9dd6105e8d5551531623225/thinc-8.3.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4f26d1eec9b2a6a8f2e0298a5515d13eb06d70730d0d9e1040bb329e12bf3fb", size = 3849253, upload-time = "2026-03-23T07:22:27.845Z" }, - { url = "https://files.pythonhosted.org/packages/7a/59/ce9c7067f1dfe5985875927de9cf7a79f9dae3e69487fd650dfba558029d/thinc-8.3.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a61a31fd0ce3c2771cf4901ba6df70e774ffe32febf1024c5b43d63575cd58fe", size = 4831163, upload-time = "2026-03-23T07:22:29.395Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a8/f57819347fc4d8bef2204d15fcbb9d7dff2d6cdd5f83d5ed91456ddacc55/thinc-8.3.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba8119daf84a12259ae4d251d36426417bafa0b34108890b4b7e2b50966bd990", size = 4986051, upload-time = "2026-03-23T07:22:30.933Z" }, - { url = "https://files.pythonhosted.org/packages/05/ef/a82214bb7c7c1e2d92b69e1a7654be90cfab180082c6108e45a98af2422c/thinc-8.3.13-cp314-cp314-win_amd64.whl", hash = "sha256:433e3826e018da489f1a8068e6de677f6eff3cc93991a599d90f12cd1bc26cdc", size = 1740382, upload-time = "2026-03-23T07:22:32.869Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ef/1648fda54e9689058335ff54f650a7a314db2a42e21af1b83949b2dc748e/thinc-8.3.13-cp314-cp314-win_arm64.whl", hash = "sha256:11754fada9ad5ba2e02d5f3f234f940e24015b82333db58372f4a6aedad9b43f", size = 1667687, upload-time = "2026-03-23T07:22:34.967Z" }, + { url = "https://files.pythonhosted.org/packages/3e/af/f7c1ebfe92eb5d27d7f2f3da67a11e2eb57bc30ab1553279af6dc65b65a8/thinc-8.3.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:77a41f66285321d20aaedaea1e87d7cd48dca6d2427bed1867ec7cba7109fc8d", size = 821097 }, + { url = "https://files.pythonhosted.org/packages/45/8f/69d7338575d98df85d0b54c0f5fc277dba72587fe9ab846ecdd12a998bcb/thinc-8.3.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3710d318b4e5460cf366a6f7b5ddbefb5d39dbd4cfa408222750fdc6c27c4411", size = 791932 }, + { url = "https://files.pythonhosted.org/packages/4b/a5/21d010c81e81e1589e5ccb4950e521804d13726e541e87f644c51815673b/thinc-8.3.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a08c87143a6d20177652dca1ec0dc815d88216d8fc62594a57e8bc45bf5ed49", size = 3854219 }, + { url = "https://files.pythonhosted.org/packages/f9/ff/6914bf370bd1d604d89e6dfb46b97d10cd9b00d42ff8c036283e92314a8c/thinc-8.3.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b5ec9ff313819e7d8667794a3559463fa89ff45aaa73e3fd8d6273b1e0d7a7f", size = 3903307 }, + { url = "https://files.pythonhosted.org/packages/f3/3d/5572b47fa155fb3388c071515b74024fa17a6efd1df9406da378f0aa84ef/thinc-8.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c9a48f2bc1e04f138240ed5f9b815a9141a5de26accd0f08fa0137fcefed258", size = 4836882 }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a8d77c7bac089697c6df302cc3c936a1ab36a4720deae889e6f1dbcbd0eb/thinc-8.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79a29a44d76bd02f5ac0624268c6e42b3576ae472c791a8ae9c2d813ae789b59", size = 5033398 }, + { url = "https://files.pythonhosted.org/packages/21/82/5651bb1f904d04220fc7670035ada921bf0638e2cff6444d67c12887a968/thinc-8.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:ed1dc709ac4f2f03b710457889e4e02f05de51bc8456980c241d0b28798bc7cb", size = 1721248 }, + { url = "https://files.pythonhosted.org/packages/94/8d/683703de021ffbe46833d722b70f49ffbbca8e5bd6876256977555d92d7d/thinc-8.3.13-cp312-cp312-win_arm64.whl", hash = "sha256:c6a049703a6011c8fe26ee41af7e70272145594140d82f79bb23de619c6a6525", size = 1645777 }, + { url = "https://files.pythonhosted.org/packages/af/b9/7b46942176df459d1804a9e77b0976f7c56f3abf3ec7485d0e5f836a0382/thinc-8.3.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2811dfd8d46d8b5d3b39051b23e64006b2994a5143b1978b436938018792af8", size = 817337 }, + { url = "https://files.pythonhosted.org/packages/a7/79/53085a72cd8f4fc4e6e313d05ea5aa98e870684f4a0fb318a9875fc0a964/thinc-8.3.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5593e6300cb1ebe0c0e546e9c9fb49e7c2627a0aa688795cd4f995a8b820d2ec", size = 788120 }, + { url = "https://files.pythonhosted.org/packages/9e/3e/d61b462b16da95ac6885f95bb395e672040ee594833e571a6edcffd234f5/thinc-8.3.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f697174d3fb474966ce50b430bbafa101a6d2f7ffb559dac4b5c59389ef72d22", size = 3844666 }, + { url = "https://files.pythonhosted.org/packages/78/4c/898cc654bb123734c71ec5a425c02ca34439517d01ce1c95a6563295580e/thinc-8.3.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9c7c5c104737b414c8c4ec578e67d78b6c859afe25cbc0684402e721415bd7f", size = 3890658 }, + { url = "https://files.pythonhosted.org/packages/cd/56/1abdbf0a4ad628e8a05d6516fe0745969649d805367a3dccad8ee872981b/thinc-8.3.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a99d0e242d1ccd23f9ae6bea7cd502f8626efa65c156b91d84581d0356696c3", size = 4819933 }, + { url = "https://files.pythonhosted.org/packages/f1/22/b84dbdc6be5055bbdb2a7352e2c393f67e8593c137f1b83c82bf1e062b6e/thinc-8.3.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e676edd21a747afbe3e6b9f3fca8b962e36d146ded03b070cb0c28e2dfbe9499", size = 5018099 }, + { url = "https://files.pythonhosted.org/packages/0f/a8/763cd7ba949334c9d2cddc92dadb68b344cb9546dc01b8d4a733dcaa16c1/thinc-8.3.13-cp313-cp313-win_amd64.whl", hash = "sha256:8ad40307f20e83f77af28ff5c6be0b86af7a8b251d1231c545508d2763157d8f", size = 1720309 }, + { url = "https://files.pythonhosted.org/packages/f5/15/a11f7bb3cbc97dfecf32a90552f5a8f8a5c99316a99c6c17bdabf5baf256/thinc-8.3.13-cp313-cp313-win_arm64.whl", hash = "sha256:723949cab11d1925c15447928513a718276316cec6e0de28337cca0a62be0521", size = 1644606 }, + { url = "https://files.pythonhosted.org/packages/80/40/f4937d113912c6d669ffe982356ab29dcb6c7fe3be926a15981dbbb6a91c/thinc-8.3.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7badb0be4825535e6362c19e8a41872b65409e9da46d3453a391b843a0720865", size = 817024 }, + { url = "https://files.pythonhosted.org/packages/d2/00/4d4ed1a11ba2920b85a03a0683b16d97dc5beb2e78078dbf0e13e43bcea7/thinc-8.3.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:565300b7e13de799e5abff00d445f537e9256cf7da4dcb0d0f005fc16748a29e", size = 792096 }, + { url = "https://files.pythonhosted.org/packages/44/5d/dc33d6932be8721af2ef76b4a3a6e8020648630eabae61fb916d2a861d1d/thinc-8.3.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c17cef1900a1aba7e1487493d16b8aa0a8633116f1b2a51c6649a4000697f17b", size = 3842215 }, + { url = "https://files.pythonhosted.org/packages/af/bc/a6d37d8dadc2c5b524f51192413481160c42c9dd6105e8d5551531623225/thinc-8.3.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4f26d1eec9b2a6a8f2e0298a5515d13eb06d70730d0d9e1040bb329e12bf3fb", size = 3849253 }, + { url = "https://files.pythonhosted.org/packages/7a/59/ce9c7067f1dfe5985875927de9cf7a79f9dae3e69487fd650dfba558029d/thinc-8.3.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a61a31fd0ce3c2771cf4901ba6df70e774ffe32febf1024c5b43d63575cd58fe", size = 4831163 }, + { url = "https://files.pythonhosted.org/packages/4f/a8/f57819347fc4d8bef2204d15fcbb9d7dff2d6cdd5f83d5ed91456ddacc55/thinc-8.3.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba8119daf84a12259ae4d251d36426417bafa0b34108890b4b7e2b50966bd990", size = 4986051 }, + { url = "https://files.pythonhosted.org/packages/05/ef/a82214bb7c7c1e2d92b69e1a7654be90cfab180082c6108e45a98af2422c/thinc-8.3.13-cp314-cp314-win_amd64.whl", hash = "sha256:433e3826e018da489f1a8068e6de677f6eff3cc93991a599d90f12cd1bc26cdc", size = 1740382 }, + { url = "https://files.pythonhosted.org/packages/9f/ef/1648fda54e9689058335ff54f650a7a314db2a42e21af1b83949b2dc748e/thinc-8.3.13-cp314-cp314-win_arm64.whl", hash = "sha256:11754fada9ad5ba2e02d5f3f234f940e24015b82333db58372f4a6aedad9b43f", size = 1667687 }, ] [[package]] name = "threadpoolctl" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, ] [[package]] @@ -8435,43 +8418,43 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, - { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, - { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, - { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, - { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, - { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, - { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, - { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, - { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, - { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, - { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, - { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, - { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, - { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, - { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, - { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, - { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, - { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, - { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, - { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, - { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, - { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, - { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, - { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, - { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728 }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049 }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008 }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665 }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230 }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688 }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694 }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802 }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995 }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948 }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986 }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222 }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097 }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117 }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712 }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725 }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875 }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451 }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794 }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777 }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188 }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978 }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271 }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216 }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860 }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567 }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067 }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855 }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022 }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736 }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908 }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706 }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667 }, ] [[package]] @@ -8485,27 +8468,27 @@ dependencies = [ { name = "torch", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "torchvision", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/1e/e924b3b2326a856aaf68586f9c52a5fc81ef45715eca408393b68c597e0e/timm-1.0.26.tar.gz", hash = "sha256:f66f082f2f381cf68431c22714c8b70f723837fa2a185b155961eab90f2d5b10", size = 2419859, upload-time = "2026-03-23T18:12:10.272Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/1e/e924b3b2326a856aaf68586f9c52a5fc81ef45715eca408393b68c597e0e/timm-1.0.26.tar.gz", hash = "sha256:f66f082f2f381cf68431c22714c8b70f723837fa2a185b155961eab90f2d5b10", size = 2419859 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/e9/bebf3d50e3fc847378988235f87c37ad3ac26d386041ab915d15e92025cd/timm-1.0.26-py3-none-any.whl", hash = "sha256:985c330de5ccc3a2aa0224eb7272e6a336084702390bb7e3801f3c91603d3683", size = 2568766, upload-time = "2026-03-23T18:12:08.062Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e9/bebf3d50e3fc847378988235f87c37ad3ac26d386041ab915d15e92025cd/timm-1.0.26-py3-none-any.whl", hash = "sha256:985c330de5ccc3a2aa0224eb7272e6a336084702390bb7e3801f3c91603d3683", size = 2568766 }, ] [[package]] name = "tinytag" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/59/8a8cb2331e2602b53e4dc06960f57d1387a2b18e7efd24e5f9cb60ea4925/tinytag-2.2.1.tar.gz", hash = "sha256:e6d06610ebe7cd66fd07be2d3b9495914ab32654a5e47657bb8cd44c2484523c", size = 38214, upload-time = "2026-03-15T18:48:01.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/59/8a8cb2331e2602b53e4dc06960f57d1387a2b18e7efd24e5f9cb60ea4925/tinytag-2.2.1.tar.gz", hash = "sha256:e6d06610ebe7cd66fd07be2d3b9495914ab32654a5e47657bb8cd44c2484523c", size = 38214 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/34/d50e338631baaf65ec5396e70085e5de0b52b24b28db1ffbc1c6e82190dc/tinytag-2.2.1-py3-none-any.whl", hash = "sha256:ed8b1e6d25367937e3321e054f4974f9abfde1a3e0a538824c87da377130c2b6", size = 32927, upload-time = "2026-03-15T18:47:59.613Z" }, + { url = "https://files.pythonhosted.org/packages/ce/34/d50e338631baaf65ec5396e70085e5de0b52b24b28db1ffbc1c6e82190dc/tinytag-2.2.1-py3-none-any.whl", hash = "sha256:ed8b1e6d25367937e3321e054f4974f9abfde1a3e0a538824c87da377130c2b6", size = 32927 }, ] [[package]] name = "tld" version = "0.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5d/76b4383ac4e5b5e254e50c09807b3e13820bed6d6c11cd540264988d6802/tld-0.13.2.tar.gz", hash = "sha256:d983fa92b9d717400742fca844e29d5e18271079c7bcfabf66d01b39b4a14345", size = 467175, upload-time = "2026-03-06T23:50:34.498Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5d/76b4383ac4e5b5e254e50c09807b3e13820bed6d6c11cd540264988d6802/tld-0.13.2.tar.gz", hash = "sha256:d983fa92b9d717400742fca844e29d5e18271079c7bcfabf66d01b39b4a14345", size = 467175 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743, upload-time = "2026-03-06T23:50:32.465Z" }, + { url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743 }, ] [[package]] @@ -8515,32 +8498,32 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275 }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472 }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736 }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835 }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673 }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818 }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195 }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982 }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245 }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069 }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263 }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429 }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363 }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786 }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133 }, ] [[package]] name = "toml" version = "0.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, ] [[package]] @@ -8564,26 +8547,26 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, - { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, - { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, - { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, - { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, - { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, - { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, - { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, - { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, - { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, - { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, - { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338 }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115 }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279 }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047 }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801 }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382 }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509 }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842 }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574 }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324 }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026 }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702 }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442 }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385 }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756 }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300 }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460 }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835 }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405 }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991 }, ] [[package]] @@ -8596,43 +8579,43 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502, upload-time = "2026-03-23T18:12:57.326Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944, upload-time = "2026-03-23T18:12:42.805Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205, upload-time = "2026-03-23T18:12:54.654Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155, upload-time = "2026-03-23T18:12:32.652Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809, upload-time = "2026-03-23T18:12:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494, upload-time = "2026-03-23T18:12:46.062Z" }, - { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747, upload-time = "2026-03-23T18:12:36.815Z" }, - { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880, upload-time = "2026-03-23T18:12:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973, upload-time = "2026-03-23T18:12:48.781Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679, upload-time = "2026-03-23T18:12:26.196Z" }, - { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138, upload-time = "2026-03-23T18:12:35.327Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202, upload-time = "2026-03-23T18:12:41.423Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813, upload-time = "2026-03-23T18:12:39.636Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777, upload-time = "2026-03-23T18:12:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174, upload-time = "2026-03-23T18:12:29.565Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469, upload-time = "2026-03-23T18:12:24.44Z" }, - { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826, upload-time = "2026-03-23T18:12:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089, upload-time = "2026-03-23T18:12:31.394Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704, upload-time = "2026-03-23T18:12:20.301Z" }, - { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275, upload-time = "2026-03-23T18:12:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502 }, + { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944 }, + { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205 }, + { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155 }, + { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809 }, + { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494 }, + { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747 }, + { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880 }, + { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973 }, + { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679 }, + { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138 }, + { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202 }, + { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813 }, + { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777 }, + { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174 }, + { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469 }, + { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826 }, + { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089 }, + { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704 }, + { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275 }, ] [[package]] name = "tornado" version = "6.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, - { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, - { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, - { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983 }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246 }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229 }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192 }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039 }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445 }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582 }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990 }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016 }, ] [[package]] @@ -8642,9 +8625,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, ] [[package]] @@ -8660,9 +8643,9 @@ dependencies = [ { name = "lxml" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/25/e3ebeefdebfdfae8c4a4396f5a6ea51fc6fa0831d63ce338e5090a8003dc/trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247", size = 253404, upload-time = "2024-12-03T15:23:24.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/25/e3ebeefdebfdfae8c4a4396f5a6ea51fc6fa0831d63ce338e5090a8003dc/trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247", size = 253404 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/b6/097367f180b6383a3581ca1b86fcae284e52075fa941d1232df35293363c/trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d", size = 132557, upload-time = "2024-12-03T15:23:21.41Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b6/097367f180b6383a3581ca1b86fcae284e52075fa941d1232df35293363c/trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d", size = 132557 }, ] [[package]] @@ -8681,69 +8664,69 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498 }, ] [[package]] name = "tree-sitter" version = "0.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941, upload-time = "2025-09-25T17:37:34.813Z" }, - { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699, upload-time = "2025-09-25T17:37:36.349Z" }, - { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125, upload-time = "2025-09-25T17:37:37.725Z" }, - { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418, upload-time = "2025-09-25T17:37:38.922Z" }, - { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250, upload-time = "2025-09-25T17:37:40.039Z" }, - { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156, upload-time = "2025-09-25T17:37:41.132Z" }, - { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984, upload-time = "2025-09-25T17:37:42.074Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926, upload-time = "2025-09-25T17:37:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712, upload-time = "2025-09-25T17:37:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873, upload-time = "2025-09-25T17:37:45.477Z" }, - { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313, upload-time = "2025-09-25T17:37:46.68Z" }, - { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370, upload-time = "2025-09-25T17:37:47.993Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157, upload-time = "2025-09-25T17:37:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975, upload-time = "2025-09-25T17:37:49.922Z" }, - { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776, upload-time = "2025-09-25T17:37:50.898Z" }, - { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732, upload-time = "2025-09-25T17:37:51.877Z" }, - { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456, upload-time = "2025-09-25T17:37:52.925Z" }, - { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772, upload-time = "2025-09-25T17:37:54.647Z" }, - { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522, upload-time = "2025-09-25T17:37:55.833Z" }, - { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864, upload-time = "2025-09-25T17:37:57.453Z" }, - { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470, upload-time = "2025-09-25T17:37:58.431Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941 }, + { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699 }, + { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125 }, + { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418 }, + { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250 }, + { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156 }, + { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984 }, + { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926 }, + { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712 }, + { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873 }, + { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313 }, + { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370 }, + { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157 }, + { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975 }, + { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776 }, + { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732 }, + { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456 }, + { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772 }, + { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522 }, + { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864 }, + { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470 }, ] [[package]] name = "tree-sitter-c" version = "0.24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/f5/ba8cd08d717277551ade8537d3aa2a94b907c6c6e0fbcf4e4d8b1c747fa3/tree_sitter_c-0.24.1.tar.gz", hash = "sha256:7d2d0cda0b8dda428c81440c1e94367f9f13548eedca3f49768bde66b1422ad6", size = 228014, upload-time = "2025-05-24T17:32:58.384Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/f5/ba8cd08d717277551ade8537d3aa2a94b907c6c6e0fbcf4e4d8b1c747fa3/tree_sitter_c-0.24.1.tar.gz", hash = "sha256:7d2d0cda0b8dda428c81440c1e94367f9f13548eedca3f49768bde66b1422ad6", size = 228014 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/c7/c817be36306e457c2d36cc324789046390d9d8c555c38772429ffdb7d361/tree_sitter_c-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9c06ac26a1efdcc8b26a8a6970fbc6997c4071857359e5837d4c42892d45fe1e", size = 80940, upload-time = "2025-05-24T17:32:49.967Z" }, - { url = "https://files.pythonhosted.org/packages/7a/42/283909467290b24fdbc29bb32ee20e409a19a55002b43175d66d091ca1a4/tree_sitter_c-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:942bcd7cbecd810dcf7ca6f8f834391ebf0771a89479646d891ba4ca2fdfdc88", size = 86304, upload-time = "2025-05-24T17:32:51.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/53/fb4f61d4e5f15ec3da85774a4df8e58d3b5b73036cf167f0203b4dd9d158/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a74cfd7a11ca5a961fafd4d751892ee65acae667d2818968a6f079397d8d28c", size = 109996, upload-time = "2025-05-24T17:32:52.119Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e8/fc541d34ee81c386c5453c2596c1763e8e9cd7cb0725f39d7dfa2276afa4/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a807705a3978911dc7ee26a7ad36dcfacb6adfc13c190d496660ec9bd66707", size = 98137, upload-time = "2025-05-24T17:32:53.361Z" }, - { url = "https://files.pythonhosted.org/packages/32/c6/d0563319cae0d5b5780a92e2806074b24afea2a07aa4c10599b899bda3ec/tree_sitter_c-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:789781afcb710df34144f7e2a20cd80e325114b9119e3956c6bd1dd2d365df98", size = 94148, upload-time = "2025-05-24T17:32:54.855Z" }, - { url = "https://files.pythonhosted.org/packages/50/5a/6361df7f3fa2310c53a0d26b4702a261c332da16fa9d801e381e3a86e25f/tree_sitter_c-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:290bff0f9c79c966496ebae45042f77543e6e4aea725f40587a8611d566231a8", size = 84703, upload-time = "2025-05-24T17:32:56.084Z" }, - { url = "https://files.pythonhosted.org/packages/22/6a/210a302e8025ac492cbaea58d3720d66b7d8034c5d747ac5e4d2d235aa25/tree_sitter_c-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:d46bbda06f838c2dcb91daf767813671fd366b49ad84ff37db702129267b46e1", size = 82715, upload-time = "2025-05-24T17:32:57.248Z" }, + { url = "https://files.pythonhosted.org/packages/15/c7/c817be36306e457c2d36cc324789046390d9d8c555c38772429ffdb7d361/tree_sitter_c-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9c06ac26a1efdcc8b26a8a6970fbc6997c4071857359e5837d4c42892d45fe1e", size = 80940 }, + { url = "https://files.pythonhosted.org/packages/7a/42/283909467290b24fdbc29bb32ee20e409a19a55002b43175d66d091ca1a4/tree_sitter_c-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:942bcd7cbecd810dcf7ca6f8f834391ebf0771a89479646d891ba4ca2fdfdc88", size = 86304 }, + { url = "https://files.pythonhosted.org/packages/94/53/fb4f61d4e5f15ec3da85774a4df8e58d3b5b73036cf167f0203b4dd9d158/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a74cfd7a11ca5a961fafd4d751892ee65acae667d2818968a6f079397d8d28c", size = 109996 }, + { url = "https://files.pythonhosted.org/packages/5e/e8/fc541d34ee81c386c5453c2596c1763e8e9cd7cb0725f39d7dfa2276afa4/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a807705a3978911dc7ee26a7ad36dcfacb6adfc13c190d496660ec9bd66707", size = 98137 }, + { url = "https://files.pythonhosted.org/packages/32/c6/d0563319cae0d5b5780a92e2806074b24afea2a07aa4c10599b899bda3ec/tree_sitter_c-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:789781afcb710df34144f7e2a20cd80e325114b9119e3956c6bd1dd2d365df98", size = 94148 }, + { url = "https://files.pythonhosted.org/packages/50/5a/6361df7f3fa2310c53a0d26b4702a261c332da16fa9d801e381e3a86e25f/tree_sitter_c-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:290bff0f9c79c966496ebae45042f77543e6e4aea725f40587a8611d566231a8", size = 84703 }, + { url = "https://files.pythonhosted.org/packages/22/6a/210a302e8025ac492cbaea58d3720d66b7d8034c5d747ac5e4d2d235aa25/tree_sitter_c-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:d46bbda06f838c2dcb91daf767813671fd366b49ad84ff37db702129267b46e1", size = 82715 }, ] [[package]] name = "tree-sitter-javascript" version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338, upload-time = "2025-09-01T07:13:44.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052, upload-time = "2025-09-01T07:13:36.865Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440, upload-time = "2025-09-01T07:13:37.962Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728, upload-time = "2025-09-01T07:13:38.755Z" }, - { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072, upload-time = "2025-09-01T07:13:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388, upload-time = "2025-09-01T07:13:40.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377, upload-time = "2025-09-01T07:13:41.838Z" }, - { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975, upload-time = "2025-09-01T07:13:42.828Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668, upload-time = "2025-09-01T07:13:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052 }, + { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440 }, + { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728 }, + { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072 }, + { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388 }, + { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377 }, + { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975 }, + { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668 }, ] [[package]] @@ -8754,41 +8737,41 @@ dependencies = [ { name = "tree-sitter" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/80/ec/230bda9b98fbc7a37e91e634633bb51a95aa25c3c6b92bb1a41f741c3dd3/tree_sitter_language_pack-1.3.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3efb7068e61342b731b23216447b536f2fe57f7abc7a4f291e5c22760d964350", size = 2191180, upload-time = "2026-03-27T06:55:51.528Z" }, - { url = "https://files.pythonhosted.org/packages/e5/61/531cc590cf6aa35efdc63741fc2bf17b97a6b280a08695df65b168dc3f9a/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a6921210705fd12fb67823c428c800e7903e835c5108eef06df3a09832e8a23e", size = 2369160, upload-time = "2026-03-27T06:55:53.529Z" }, - { url = "https://files.pythonhosted.org/packages/03/42/8e768d21ae85bc6982e2693e9016d75a6e06f1f98a6f4dec5bcb07120dd7/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d699b26a251769628045f298a184e86c005cc25c8a67646c0562adc929044e95", size = 2506996, upload-time = "2026-03-27T06:55:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/6e/64/4fdf78ddd2d59f3e66332d3d6a0f3460e6425045f7150ed02d37f73ef019/tree_sitter_language_pack-1.3.3-cp310-abi3-win_amd64.whl", hash = "sha256:8a0735f898fc9443c90ea824c88b9fc19588ea10567ae97c768e3f9b14b64e16", size = 2300882, upload-time = "2026-03-27T06:55:57.047Z" }, + { url = "https://files.pythonhosted.org/packages/80/ec/230bda9b98fbc7a37e91e634633bb51a95aa25c3c6b92bb1a41f741c3dd3/tree_sitter_language_pack-1.3.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3efb7068e61342b731b23216447b536f2fe57f7abc7a4f291e5c22760d964350", size = 2191180 }, + { url = "https://files.pythonhosted.org/packages/e5/61/531cc590cf6aa35efdc63741fc2bf17b97a6b280a08695df65b168dc3f9a/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a6921210705fd12fb67823c428c800e7903e835c5108eef06df3a09832e8a23e", size = 2369160 }, + { url = "https://files.pythonhosted.org/packages/03/42/8e768d21ae85bc6982e2693e9016d75a6e06f1f98a6f4dec5bcb07120dd7/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d699b26a251769628045f298a184e86c005cc25c8a67646c0562adc929044e95", size = 2506996 }, + { url = "https://files.pythonhosted.org/packages/6e/64/4fdf78ddd2d59f3e66332d3d6a0f3460e6425045f7150ed02d37f73ef019/tree_sitter_language_pack-1.3.3-cp310-abi3-win_amd64.whl", hash = "sha256:8a0735f898fc9443c90ea824c88b9fc19588ea10567ae97c768e3f9b14b64e16", size = 2300882 }, ] [[package]] name = "tree-sitter-python" version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845, upload-time = "2025-09-11T06:47:58.159Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790, upload-time = "2025-09-11T06:47:47.652Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691, upload-time = "2025-09-11T06:47:49.038Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133, upload-time = "2025-09-11T06:47:50.499Z" }, - { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603, upload-time = "2025-09-11T06:47:51.985Z" }, - { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998, upload-time = "2025-09-11T06:47:53.046Z" }, - { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268, upload-time = "2025-09-11T06:47:54.388Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073, upload-time = "2025-09-11T06:47:55.773Z" }, - { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169, upload-time = "2025-09-11T06:47:56.747Z" }, + { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790 }, + { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691 }, + { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133 }, + { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603 }, + { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998 }, + { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268 }, + { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073 }, + { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169 }, ] [[package]] name = "tree-sitter-typescript" version = "0.23.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053, upload-time = "2024-11-11T02:36:11.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053 } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677, upload-time = "2024-11-11T02:35:58.839Z" }, - { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008, upload-time = "2024-11-11T02:36:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987, upload-time = "2024-11-11T02:36:02.669Z" }, - { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960, upload-time = "2024-11-11T02:36:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245, upload-time = "2024-11-11T02:36:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015, upload-time = "2024-11-11T02:36:07.631Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052, upload-time = "2024-11-11T02:36:09.514Z" }, + { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677 }, + { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008 }, + { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987 }, + { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960 }, + { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245 }, + { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015 }, + { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052 }, ] [[package]] @@ -8796,16 +8779,16 @@ name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, - { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, - { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, - { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243 }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850 }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521 }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450 }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087 }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296 }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577 }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063 }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804 }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994 }, ] [[package]] @@ -8823,9 +8806,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/9081fd2c0146196546954c895b467986681d0e6fdcc42810a0780ee459a8/turbopuffer-1.19.0.tar.gz", hash = "sha256:6b7ba70dae2769dd21892b4535ed63fe1e4482a650f4d0f5239d1a8f2c101660", size = 326319, upload-time = "2026-03-18T23:26:57.44Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/9081fd2c0146196546954c895b467986681d0e6fdcc42810a0780ee459a8/turbopuffer-1.19.0.tar.gz", hash = "sha256:6b7ba70dae2769dd21892b4535ed63fe1e4482a650f4d0f5239d1a8f2c101660", size = 326319 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/8b/509e5fc117eaeb7387709a8cedf471fbb943a8c0672608e2eddf317f4b02/turbopuffer-1.19.0-py3-none-any.whl", hash = "sha256:b0ad5eac6075ff2fe0d1a85bf5eb3013b4cbffc7f696b67121584ad33467a726", size = 117105, upload-time = "2026-03-18T23:26:58.726Z" }, + { url = "https://files.pythonhosted.org/packages/e2/8b/509e5fc117eaeb7387709a8cedf471fbb943a8c0672608e2eddf317f4b02/turbopuffer-1.19.0-py3-none-any.whl", hash = "sha256:b0ad5eac6075ff2fe0d1a85bf5eb3013b4cbffc7f696b67121584ad33467a726", size = 117105 }, ] [[package]] @@ -8843,9 +8826,9 @@ dependencies = [ { name = "rich" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727 }, ] [[package]] @@ -8858,9 +8841,9 @@ dependencies = [ { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/1e/a27cc02a0cd715118c71fa2aef2c687fdefc3c28d90fd0dd789c5118154c/typer-0.21.2.tar.gz", hash = "sha256:1abd95a3b675e17ff61b0838ac637fe9478d446d62ad17fa4bb81ea57cc54028", size = 120426, upload-time = "2026-02-10T19:33:46.182Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/1e/a27cc02a0cd715118c71fa2aef2c687fdefc3c28d90fd0dd789c5118154c/typer-0.21.2.tar.gz", hash = "sha256:1abd95a3b675e17ff61b0838ac637fe9478d446d62ad17fa4bb81ea57cc54028", size = 120426 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/cc/d59f893fbdfb5f58770c05febfc4086a46875f1084453621c35605cec946/typer-0.21.2-py3-none-any.whl", hash = "sha256:c3d8de54d00347ef90b82131ca946274f017cffb46683ae3883c360fa958f55c", size = 56728, upload-time = "2026-02-10T19:33:48.01Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cc/d59f893fbdfb5f58770c05febfc4086a46875f1084453621c35605cec946/typer-0.21.2-py3-none-any.whl", hash = "sha256:c3d8de54d00347ef90b82131ca946274f017cffb46683ae3883c360fa958f55c", size = 56728 }, ] [[package]] @@ -8870,18 +8853,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/5f/2e3dbae6e21be6ae026563bad96cbf76602d73aa85ea09f13419ddbdabb4/types_requests-2.33.0.20260327.tar.gz", hash = "sha256:f4f74f0b44f059e3db420ff17bd1966e3587cdd34062fe38a23cda97868f8dd8", size = 23804, upload-time = "2026-03-27T04:23:38.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/5f/2e3dbae6e21be6ae026563bad96cbf76602d73aa85ea09f13419ddbdabb4/types_requests-2.33.0.20260327.tar.gz", hash = "sha256:f4f74f0b44f059e3db420ff17bd1966e3587cdd34062fe38a23cda97868f8dd8", size = 23804 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/55/951e733616c92cb96b57554746d2f65f4464d080cc2cc093605f897aba89/types_requests-2.33.0.20260327-py3-none-any.whl", hash = "sha256:fde0712be6d7c9a4d490042d6323115baf872d9a71a22900809d0432de15776e", size = 20737, upload-time = "2026-03-27T04:23:37.813Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/951e733616c92cb96b57554746d2f65f4464d080cc2cc093605f897aba89/types_requests-2.33.0.20260327-py3-none-any.whl", hash = "sha256:fde0712be6d7c9a4d490042d6323115baf872d9a71a22900809d0432de15776e", size = 20737 }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, ] [[package]] @@ -8892,9 +8875,9 @@ dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, ] [[package]] @@ -8904,42 +8887,42 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, ] [[package]] name = "typst" version = "0.14.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/17/011059074fe6c51ed775991d5066c73443f17d49b3d4ab9c1a969dcdb5cb/typst-0.14.8.tar.gz", hash = "sha256:8ffb8d5896aa6a20a7b88ae3fa1dfcf062fdd09b5b6a0a164f92f78ad1a2d8cd", size = 62369, upload-time = "2026-02-08T02:31:21.753Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/17/011059074fe6c51ed775991d5066c73443f17d49b3d4ab9c1a969dcdb5cb/typst-0.14.8.tar.gz", hash = "sha256:8ffb8d5896aa6a20a7b88ae3fa1dfcf062fdd09b5b6a0a164f92f78ad1a2d8cd", size = 62369 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/67/af5551e95261fc425f6dbf241ec08bf1172fd10ef239787ff6e009bb2f08/typst-0.14.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4697b9de12d7b1bc85209960e1ef7e2c4947cffd7d6ef68201aea03597cf38bd", size = 22935370, upload-time = "2026-02-08T02:30:30.418Z" }, - { url = "https://files.pythonhosted.org/packages/6f/93/cbb32c7e830a806105ee0f6d9b6c780f2736a9c75d8121602e7842a316d2/typst-0.14.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ecb523ff7e3eb68667ad693ff4c460ac58aedfbeb6514054efce2718e7563f", size = 22624078, upload-time = "2026-02-08T02:30:33.762Z" }, - { url = "https://files.pythonhosted.org/packages/77/38/070c068442a8be93125366b27e5cf1a6b1dd62c85dab62bd6d4355643d29/typst-0.14.8-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9db137ca037bd12c0ebbbbfa1190fffaa75a2043d04adacb273cc98f0265a32", size = 26894087, upload-time = "2026-02-08T02:30:36.876Z" }, - { url = "https://files.pythonhosted.org/packages/ee/32/8754413c4cdf631c51e16690775dcfd28e783c1ccc0efc71d92ef73e0db3/typst-0.14.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8c4ac751c3480b0fcfc7fce273025bb7392654db5a3aa65904f8678192c54f8", size = 26489748, upload-time = "2026-02-08T02:30:40.169Z" }, - { url = "https://files.pythonhosted.org/packages/27/2b/3b1256033c7b971d0c79af41fadff552c1df7a9f9774a540f1a2ede97937/typst-0.14.8-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37da60ec4afcd82b55664612aab10cac11a8ebc075686057705261de9e901523", size = 28023293, upload-time = "2026-02-08T02:30:43.222Z" }, - { url = "https://files.pythonhosted.org/packages/61/1b/8769c89998299525e4b04fddce1b15977d18051695c65760203b55f7ed47/typst-0.14.8-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1cfbc313ba3b883da8c45233506766a503da307057a5d8d39e360023733c463", size = 27109055, upload-time = "2026-02-08T02:30:46.214Z" }, - { url = "https://files.pythonhosted.org/packages/51/97/b1f43e29051401289b6ef37398eb83d78584f52e0b213f8675b9b10b0c0b/typst-0.14.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b509e7a599dd07e36e18495f0258511de527f5e0dc145622025d204c84db5246", size = 26017464, upload-time = "2026-02-08T02:30:49.271Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ca/44732fc1e486be822ba65ee9a02f0bc5f28d1cb08284c9dd1286d975f9ce/typst-0.14.8-cp314-cp314t-win_amd64.whl", hash = "sha256:10710c58dbc8820a954970ba5d0af5611c7c57f8ddacfebb1a85ddb6449f01eb", size = 21471708, upload-time = "2026-02-08T02:30:54.185Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cb/e49219a75d39ce866ae5d64e0a1d8d712b394ed3a1e7de3a8f4a35cde78e/typst-0.14.8-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f47fe029f6ebe907f981ce0cb5208eab27eaf7342e319e6c798ac1dbae976f58", size = 22936285, upload-time = "2026-02-08T02:30:57.338Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6b/d36f312c32b70303abd88d0abe6ffb50f8f7fcc0b457c914c78d791ed934/typst-0.14.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:aba11243463f6994ca1140b8515e70be1a98fd3025ae3211b84103499b0c5a5a", size = 22632767, upload-time = "2026-02-08T02:31:00.454Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b4/87d2d24078b94645ba8788c8b4a5bbab6a3c779370141c31a02e2003ee0f/typst-0.14.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544fcd9ce55b140115d7442b3661c45897778650c307e2eb0749efed29bbfcea", size = 26907232, upload-time = "2026-02-08T02:31:03.528Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e8/3efdebcf37639daa4799e7a4c833a280f14685f7e6058fed576c6fb2e722/typst-0.14.8-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a296f85bf0d27043b031d1d2d74a34802e4876a8936f70784fbe99021b0dad4d", size = 26501791, upload-time = "2026-02-08T02:31:06.898Z" }, - { url = "https://files.pythonhosted.org/packages/7e/28/094d4b9f0ff4ee81f88eee2df00dbcfbd961070df981973bc385a1544ff8/typst-0.14.8-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e3891a2e5551017c9030dd6de31587a29b97c18464df6bcff05f30f7cdab677", size = 28028881, upload-time = "2026-02-08T02:31:10.437Z" }, - { url = "https://files.pythonhosted.org/packages/11/a1/15cd399dfc5ce0ea9e05d5bbc274c95f8ecababc04b4210bae8d583fe454/typst-0.14.8-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a19cf938607c73fd8c5245a7cb32c94af413080a3d747fcf7e16df88713c686", size = 27128399, upload-time = "2026-02-08T02:31:13.597Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6f/ff1c58dac9245d4c355bfced006090b14a2f17497e9cf79a84d9d720663a/typst-0.14.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12766a83e390377008722a8c80afdd9195a297261fd3c9d1f3720f9aecd2b19", size = 26026753, upload-time = "2026-02-08T02:31:16.591Z" }, - { url = "https://files.pythonhosted.org/packages/a9/42/db15d775c09f0da92191ea1b50cee056e46b599bb5524e2d8ff51f973765/typst-0.14.8-cp38-abi3-win_amd64.whl", hash = "sha256:66eb2ebfe13275cf2a63ed7ff261eb5af3da5293077a5d6ca16e27a96d0d2f5e", size = 21475900, upload-time = "2026-02-08T02:31:19.484Z" }, + { url = "https://files.pythonhosted.org/packages/d2/67/af5551e95261fc425f6dbf241ec08bf1172fd10ef239787ff6e009bb2f08/typst-0.14.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4697b9de12d7b1bc85209960e1ef7e2c4947cffd7d6ef68201aea03597cf38bd", size = 22935370 }, + { url = "https://files.pythonhosted.org/packages/6f/93/cbb32c7e830a806105ee0f6d9b6c780f2736a9c75d8121602e7842a316d2/typst-0.14.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ecb523ff7e3eb68667ad693ff4c460ac58aedfbeb6514054efce2718e7563f", size = 22624078 }, + { url = "https://files.pythonhosted.org/packages/77/38/070c068442a8be93125366b27e5cf1a6b1dd62c85dab62bd6d4355643d29/typst-0.14.8-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9db137ca037bd12c0ebbbbfa1190fffaa75a2043d04adacb273cc98f0265a32", size = 26894087 }, + { url = "https://files.pythonhosted.org/packages/ee/32/8754413c4cdf631c51e16690775dcfd28e783c1ccc0efc71d92ef73e0db3/typst-0.14.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8c4ac751c3480b0fcfc7fce273025bb7392654db5a3aa65904f8678192c54f8", size = 26489748 }, + { url = "https://files.pythonhosted.org/packages/27/2b/3b1256033c7b971d0c79af41fadff552c1df7a9f9774a540f1a2ede97937/typst-0.14.8-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37da60ec4afcd82b55664612aab10cac11a8ebc075686057705261de9e901523", size = 28023293 }, + { url = "https://files.pythonhosted.org/packages/61/1b/8769c89998299525e4b04fddce1b15977d18051695c65760203b55f7ed47/typst-0.14.8-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1cfbc313ba3b883da8c45233506766a503da307057a5d8d39e360023733c463", size = 27109055 }, + { url = "https://files.pythonhosted.org/packages/51/97/b1f43e29051401289b6ef37398eb83d78584f52e0b213f8675b9b10b0c0b/typst-0.14.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b509e7a599dd07e36e18495f0258511de527f5e0dc145622025d204c84db5246", size = 26017464 }, + { url = "https://files.pythonhosted.org/packages/5b/ca/44732fc1e486be822ba65ee9a02f0bc5f28d1cb08284c9dd1286d975f9ce/typst-0.14.8-cp314-cp314t-win_amd64.whl", hash = "sha256:10710c58dbc8820a954970ba5d0af5611c7c57f8ddacfebb1a85ddb6449f01eb", size = 21471708 }, + { url = "https://files.pythonhosted.org/packages/5b/cb/e49219a75d39ce866ae5d64e0a1d8d712b394ed3a1e7de3a8f4a35cde78e/typst-0.14.8-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f47fe029f6ebe907f981ce0cb5208eab27eaf7342e319e6c798ac1dbae976f58", size = 22936285 }, + { url = "https://files.pythonhosted.org/packages/f8/6b/d36f312c32b70303abd88d0abe6ffb50f8f7fcc0b457c914c78d791ed934/typst-0.14.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:aba11243463f6994ca1140b8515e70be1a98fd3025ae3211b84103499b0c5a5a", size = 22632767 }, + { url = "https://files.pythonhosted.org/packages/6f/b4/87d2d24078b94645ba8788c8b4a5bbab6a3c779370141c31a02e2003ee0f/typst-0.14.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544fcd9ce55b140115d7442b3661c45897778650c307e2eb0749efed29bbfcea", size = 26907232 }, + { url = "https://files.pythonhosted.org/packages/bc/e8/3efdebcf37639daa4799e7a4c833a280f14685f7e6058fed576c6fb2e722/typst-0.14.8-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a296f85bf0d27043b031d1d2d74a34802e4876a8936f70784fbe99021b0dad4d", size = 26501791 }, + { url = "https://files.pythonhosted.org/packages/7e/28/094d4b9f0ff4ee81f88eee2df00dbcfbd961070df981973bc385a1544ff8/typst-0.14.8-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e3891a2e5551017c9030dd6de31587a29b97c18464df6bcff05f30f7cdab677", size = 28028881 }, + { url = "https://files.pythonhosted.org/packages/11/a1/15cd399dfc5ce0ea9e05d5bbc274c95f8ecababc04b4210bae8d583fe454/typst-0.14.8-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a19cf938607c73fd8c5245a7cb32c94af413080a3d747fcf7e16df88713c686", size = 27128399 }, + { url = "https://files.pythonhosted.org/packages/4c/6f/ff1c58dac9245d4c355bfced006090b14a2f17497e9cf79a84d9d720663a/typst-0.14.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12766a83e390377008722a8c80afdd9195a297261fd3c9d1f3720f9aecd2b19", size = 26026753 }, + { url = "https://files.pythonhosted.org/packages/a9/42/db15d775c09f0da92191ea1b50cee056e46b599bb5524e2d8ff51f973765/typst-0.14.8-cp38-abi3-win_amd64.whl", hash = "sha256:66eb2ebfe13275cf2a63ed7ff261eb5af3da5293077a5d6ca16e27a96d0d2f5e", size = 21475900 }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, ] [[package]] @@ -8949,9 +8932,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 }, ] [[package]] @@ -8983,9 +8966,9 @@ dependencies = [ { name = "unstructured-client" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/e6/fbef61517d130af1def3b81681e253a5679f19de2f04e439afbbf1f021e0/unstructured-0.21.5.tar.gz", hash = "sha256:3e220d0c2b9c8ec12c99767162b95ab0acfca75e979b82c66c15ca15caa60139", size = 1501811, upload-time = "2026-02-24T15:29:27.84Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/e6/fbef61517d130af1def3b81681e253a5679f19de2f04e439afbbf1f021e0/unstructured-0.21.5.tar.gz", hash = "sha256:3e220d0c2b9c8ec12c99767162b95ab0acfca75e979b82c66c15ca15caa60139", size = 1501811 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/b6/7e6dd60bde81d5a4d4ddf426f566a5d1b4c30490053caed69e47f55c676f/unstructured-0.21.5-py3-none-any.whl", hash = "sha256:d88a277c368462b69a8843b9cb22476f3cc4d0a58455536520359387224b3366", size = 1554925, upload-time = "2026-02-24T15:29:26.009Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b6/7e6dd60bde81d5a4d4ddf426f566a5d1b4c30490053caed69e47f55c676f/unstructured-0.21.5-py3-none-any.whl", hash = "sha256:d88a277c368462b69a8843b9cb22476f3cc4d0a58455536520359387224b3366", size = 1554925 }, ] [package.optional-dependencies] @@ -9023,9 +9006,9 @@ dependencies = [ { name = "pypdfium2" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/ca/73904d53e486af2f1d9d8baaf43d2a74b3d67e5f533834f5d51056471339/unstructured_client-0.42.12.tar.gz", hash = "sha256:50eb6717d8c6513b14b309fce8d6551354e433da982b7a9161a889d8e6a11166", size = 94714, upload-time = "2026-03-25T20:24:21.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/ca/73904d53e486af2f1d9d8baaf43d2a74b3d67e5f533834f5d51056471339/unstructured_client-0.42.12.tar.gz", hash = "sha256:50eb6717d8c6513b14b309fce8d6551354e433da982b7a9161a889d8e6a11166", size = 94714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/80/fbf02ec3c566a3e383a5649385096834a2a981832f1432c3a8797b29185a/unstructured_client-0.42.12-py3-none-any.whl", hash = "sha256:fe6f217066a0c308ba7213185524506dbfc3bb9d35df0ab79549291e9728a012", size = 220154, upload-time = "2026-03-25T20:24:20.288Z" }, + { url = "https://files.pythonhosted.org/packages/21/80/fbf02ec3c566a3e383a5649385096834a2a981832f1432c3a8797b29185a/unstructured_client-0.42.12-py3-none-any.whl", hash = "sha256:fe6f217066a0c308ba7213185524506dbfc3bb9d35df0ab79549291e9728a012", size = 220154 }, ] [[package]] @@ -9050,9 +9033,9 @@ dependencies = [ { name = "torch", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "transformers", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/10/8f3bccfa9f1e0101a402ae1f529e07876541c6b18004747f0e793ed41f9e/unstructured_inference-1.2.0.tar.gz", hash = "sha256:19ca28512f3649c70a759cf2a4e98663e942a1b83c1acdb9506b0445f4862f23", size = 45732, upload-time = "2026-01-30T20:57:58.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/10/8f3bccfa9f1e0101a402ae1f529e07876541c6b18004747f0e793ed41f9e/unstructured_inference-1.2.0.tar.gz", hash = "sha256:19ca28512f3649c70a759cf2a4e98663e942a1b83c1acdb9506b0445f4862f23", size = 45732 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/3b/349cd091b590a6f1dbfebcb5fee0ea7b0b6ef6520df58794c9582567a24f/unstructured_inference-1.2.0-py3-none-any.whl", hash = "sha256:60a1635aa8e97a9e7daed1a129836f51c26588e0d2062c9cc6a5a17e6d40cb6a", size = 49443, upload-time = "2026-01-30T20:57:56.617Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3b/349cd091b590a6f1dbfebcb5fee0ea7b0b6ef6520df58794c9582567a24f/unstructured_inference-1.2.0-py3-none-any.whl", hash = "sha256:60a1635aa8e97a9e7daed1a129836f51c26588e0d2062c9cc6a5a17e6d40cb6a", size = 49443 }, ] [[package]] @@ -9063,49 +9046,49 @@ dependencies = [ { name = "packaging" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/b1/4b3a976b76549f22c3f5493a622603617cbe08804402978e1dac9c387997/unstructured.pytesseract-0.3.15.tar.gz", hash = "sha256:4b81bc76cfff4e2ef37b04863f0e48bd66184c0b39c3b2b4e017483bca1a7394", size = 15703, upload-time = "2025-03-05T00:59:17.516Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/b1/4b3a976b76549f22c3f5493a622603617cbe08804402978e1dac9c387997/unstructured.pytesseract-0.3.15.tar.gz", hash = "sha256:4b81bc76cfff4e2ef37b04863f0e48bd66184c0b39c3b2b4e017483bca1a7394", size = 15703 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/6d/adb955ecf60811a3735d508974bbb5358e7745b635dc001329267529c6f2/unstructured.pytesseract-0.3.15-py3-none-any.whl", hash = "sha256:a3f505c5efb7ff9f10379051a7dd6aa624b3be6b0f023ed6767cc80d0b1613d1", size = 14992, upload-time = "2025-03-05T00:59:15.962Z" }, + { url = "https://files.pythonhosted.org/packages/10/6d/adb955ecf60811a3735d508974bbb5358e7745b635dc001329267529c6f2/unstructured.pytesseract-0.3.15-py3-none-any.whl", hash = "sha256:a3f505c5efb7ff9f10379051a7dd6aa624b3be6b0f023ed6767cc80d0b1613d1", size = 14992 }, ] [[package]] name = "uritemplate" version = "4.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488 }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, ] [[package]] name = "uuid-utils" version = "0.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, - { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, - { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, - { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, - { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, - { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, - { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, - { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679 }, + { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346 }, + { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714 }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914 }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609 }, + { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699 }, + { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205 }, + { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836 }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260 }, + { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824 }, + { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407 }, + { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476 }, + { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147 }, + { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132 }, ] [[package]] @@ -9116,9 +9099,9 @@ dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830 }, ] [package.optional-dependencies] @@ -9136,41 +9119,41 @@ standard = [ 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" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250 } wheels = [ - { 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/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, ] [[package]] name = "validators" version = "0.35.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399, upload-time = "2025-05-01T05:42:06.7Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712, upload-time = "2025-05-01T05:42:04.203Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712 }, ] [[package]] @@ -9184,15 +9167,15 @@ dependencies = [ { name = "psycopg2-binary" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/87/9fb55aff1e18278c2a0d93ba48432e060086702e258e7e13068a31376548/vecs-0.4.5.tar.gz", hash = "sha256:7cd3ab65cf88f5869d49f70ae7385e844c4915700da1f2299c938afa56148cb6", size = 22036, upload-time = "2024-12-13T20:53:50.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/87/9fb55aff1e18278c2a0d93ba48432e060086702e258e7e13068a31376548/vecs-0.4.5.tar.gz", hash = "sha256:7cd3ab65cf88f5869d49f70ae7385e844c4915700da1f2299c938afa56148cb6", size = 22036 } [[package]] name = "vine" version = "5.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636 }, ] [[package]] @@ -9211,9 +9194,9 @@ dependencies = [ { name = "tenacity" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485, upload-time = "2025-12-16T18:43:05.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/64/89f6325666d6836979f94ac88b96fefc7527e02e61abc81359843585e088/voyageai-0.3.7-py3-none-any.whl", hash = "sha256:909f6c033001e5a3b3caf970525bf3614a1bfef9003cf3c3b68207dfdb53e86d", size = 34691, upload-time = "2025-12-16T18:43:04.073Z" }, + { url = "https://files.pythonhosted.org/packages/60/64/89f6325666d6836979f94ac88b96fefc7527e02e61abc81359843585e088/voyageai-0.3.7-py3-none-any.whl", hash = "sha256:909f6c033001e5a3b3caf970525bf3614a1bfef9003cf3c3b68207dfdb53e86d", size = 34691 }, ] [[package]] @@ -9223,9 +9206,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880 }, ] [[package]] @@ -9235,67 +9218,67 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440 } wheels = [ - { 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/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042 }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410 }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162 }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078 }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664 }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, ] [[package]] @@ -9305,18 +9288,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bracex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854 }, ] [[package]] name = "wcwidth" version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189 }, ] [[package]] @@ -9334,9 +9317,9 @@ dependencies = [ { name = "typer" }, { name = "wasabi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/e5/e272bb9a045105a1fdf4b798d8086f5932a178f4d738f17a74f5c9e0ae9a/weasel-1.0.0.tar.gz", hash = "sha256:7b129b44c90cc543b760532974ca1e4eb30dad2aa2026f57bdce66354ae610fc", size = 38682, upload-time = "2026-03-20T08:10:25.266Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/e5/e272bb9a045105a1fdf4b798d8086f5932a178f4d738f17a74f5c9e0ae9a/weasel-1.0.0.tar.gz", hash = "sha256:7b129b44c90cc543b760532974ca1e4eb30dad2aa2026f57bdce66354ae610fc", size = 38682 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/07/57ebf7a6798b016c064bd0ca81b4c6a99daa4dc377b898bc7b41eb6b5af0/weasel-1.0.0-py3-none-any.whl", hash = "sha256:89518acee027f49d743126c3502d35e6dd14f5768be5c37c9af47c171b6005cc", size = 50713, upload-time = "2026-03-20T08:10:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/0a/07/57ebf7a6798b016c064bd0ca81b4c6a99daa4dc377b898bc7b41eb6b5af0/weasel-1.0.0-py3-none-any.whl", hash = "sha256:89518acee027f49d743126c3502d35e6dd14f5768be5c37c9af47c171b6005cc", size = 50713 }, ] [[package]] @@ -9352,217 +9335,217 @@ dependencies = [ { name = "pydantic" }, { name = "validators" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/1c/82b560254f612f95b644849d86e092da6407f17965d61e22b583b30b72cf/weaviate_client-4.20.4.tar.gz", hash = "sha256:08703234b59e4e03739f39e740e9e88cb50cd0aa147d9408b88ea6ce995c37b6", size = 809529, upload-time = "2026-03-10T15:08:13.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/1c/82b560254f612f95b644849d86e092da6407f17965d61e22b583b30b72cf/weaviate_client-4.20.4.tar.gz", hash = "sha256:08703234b59e4e03739f39e740e9e88cb50cd0aa147d9408b88ea6ce995c37b6", size = 809529 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/d7/9461c3e7d8c44080d2307078e33dc7fefefa3171c8f930f2b83a5cbf67f2/weaviate_client-4.20.4-py3-none-any.whl", hash = "sha256:7af3a213bebcb30dcf456b0db8b6225d8926106b835d7b883276de9dc1c301fe", size = 619517, upload-time = "2026-03-10T15:08:12.047Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d7/9461c3e7d8c44080d2307078e33dc7fefefa3171c8f930f2b83a5cbf67f2/weaviate_client-4.20.4-py3-none-any.whl", hash = "sha256:7af3a213bebcb30dcf456b0db8b6225d8926106b835d7b883276de9dc1c301fe", size = 619517 }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] [[package]] name = "websocket-client" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616 }, ] [[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" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } wheels = [ - { 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/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, ] [[package]] name = "win32-setctime" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 }, ] [[package]] name = "wrapt" version = "1.17.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, - { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, - { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, - { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, - { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, - { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998 }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020 }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098 }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036 }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156 }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102 }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732 }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705 }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877 }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885 }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003 }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025 }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108 }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072 }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214 }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105 }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766 }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711 }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885 }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896 }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132 }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091 }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172 }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163 }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963 }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945 }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857 }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178 }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310 }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266 }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544 }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283 }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366 }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571 }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094 }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659 }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946 }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717 }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334 }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471 }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591 }, ] [[package]] name = "xlrd" version = "2.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, + { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555 }, ] [[package]] name = "xlsxwriter" version = "3.2.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315 }, ] [[package]] name = "xxhash" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, - { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, - { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, - { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, - { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, - { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, - { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, - { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, - { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, - { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, - { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, - { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, - { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, - { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, - { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, - { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, - { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, - { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, - { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, - { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, - { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, - { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, - { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, - { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, - { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, - { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, - { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, - { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, - { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, - { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, - { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, - { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, - { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, - { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, - { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, - { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, - { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, - { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, - { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, - { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, - { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, - { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, - { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, - { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, - { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744 }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816 }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035 }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914 }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163 }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411 }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883 }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392 }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898 }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655 }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001 }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431 }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617 }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534 }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876 }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738 }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821 }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127 }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975 }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241 }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471 }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936 }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440 }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990 }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689 }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068 }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495 }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620 }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542 }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880 }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956 }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072 }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409 }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736 }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833 }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348 }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070 }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907 }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839 }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304 }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930 }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787 }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916 }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799 }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044 }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754 }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846 }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343 }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074 }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388 }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614 }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024 }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541 }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305 }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848 }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142 }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547 }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214 }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290 }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795 }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955 }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072 }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579 }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854 }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965 }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484 }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162 }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007 }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956 }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401 }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083 }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913 }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586 }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526 }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898 }, ] [[package]] @@ -9574,99 +9557,99 @@ dependencies = [ { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, - { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, - { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, - { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, - { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, - { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, - { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, - { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, - { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, - { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, - { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, - { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, - { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, - { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, - { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, - { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, - { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, - { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, - { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, - { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, - { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, - { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, - { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, - { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, - { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, - { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, - { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, - { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, - { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, - { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, - { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, - { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, - { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, - { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737 }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029 }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310 }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587 }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528 }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339 }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061 }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132 }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289 }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950 }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960 }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703 }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325 }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067 }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285 }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359 }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674 }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879 }, + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796 }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547 }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854 }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351 }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711 }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014 }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557 }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559 }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502 }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027 }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369 }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565 }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813 }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632 }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895 }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356 }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515 }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785 }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719 }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690 }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851 }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874 }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710 }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033 }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817 }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482 }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949 }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839 }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696 }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865 }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234 }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295 }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784 }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313 }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932 }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786 }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455 }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752 }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291 }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026 }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355 }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417 }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422 }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915 }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690 }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750 }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685 }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009 }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033 }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483 }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175 }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871 }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093 }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384 }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019 }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894 }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979 }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943 }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786 }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307 }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904 }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728 }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964 }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882 }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797 }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023 }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227 }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302 }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202 }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558 }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610 }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041 }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288 }, ] [[package]] @@ -9677,73 +9660,73 @@ dependencies = [ { name = "defusedxml" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839, upload-time = "2026-01-29T09:09:17.088Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227, upload-time = "2026-01-29T09:09:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227 }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, ] [[package]] name = "zstandard" version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, - { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, - { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, - { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, - { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, - { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, - { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, - { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, - { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, - { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, - { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, - { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, - { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, - { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, - { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, - { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, - { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, - { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, - { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, - { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, - { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, - { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738 }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436 }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019 }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012 }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148 }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652 }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993 }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806 }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659 }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933 }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008 }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517 }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292 }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237 }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922 }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276 }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679 }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735 }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440 }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070 }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001 }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120 }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230 }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173 }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736 }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368 }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022 }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889 }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952 }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054 }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113 }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936 }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232 }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671 }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887 }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658 }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849 }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095 }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751 }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818 }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402 }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108 }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248 }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123 }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591 }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513 }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118 }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940 }, ] diff --git a/surfsense_browser_extension/package.json b/surfsense_browser_extension/package.json index 2f17899a8..13cd31b80 100644 --- a/surfsense_browser_extension/package.json +++ b/surfsense_browser_extension/package.json @@ -1,7 +1,7 @@ { "name": "surfsense_browser_extension", "displayName": "Surfsense Browser Extension", - "version": "0.0.25", + "version": "0.0.26", "description": "Extension to collect Browsing History for SurfSense.", "author": "https://github.com/MODSetter", "engines": { diff --git a/surfsense_desktop/package.json b/surfsense_desktop/package.json index 4ee6ea3c4..1f0e6dafc 100644 --- a/surfsense_desktop/package.json +++ b/surfsense_desktop/package.json @@ -1,7 +1,7 @@ { "name": "surfsense-desktop", "productName": "SurfSense", - "version": "0.0.25", + "version": "0.0.26", "description": "SurfSense Desktop App", "main": "dist/main.js", "scripts": { diff --git a/surfsense_web/changelog/content/2026-05-31.mdx b/surfsense_web/changelog/content/2026-05-31.mdx new file mode 100644 index 000000000..2e79effc6 --- /dev/null +++ b/surfsense_web/changelog/content/2026-05-31.mdx @@ -0,0 +1,76 @@ +--- +title: "SurfSense v0.0.26 - AI Automations: Build, Schedule & Event-Trigger AI Agents From Chat" +description: "SurfSense v0.0.26 introduces open source AI automations across your connectors: describe a workflow in plain English and SurfSense builds it, run AI agents on a schedule, or trigger them when a document lands in a folder, working across Notion, Slack, Google Drive, Gmail, GitHub, Linear, Jira and Confluence. Plus connector @-mentions in chat and a faster anonymous chat experience." +date: "2026-05-31" +tags: ["Automations", "AI Agents", "Workflow Automation", "Scheduled Workflows", "Event Triggers", "Connectors", "Notion", "Slack", "Open Source"] +version: "0.0.26" +--- + +## What's New in v0.0.26 + +v0.0.26 is our biggest workflow release yet: **AI automations** land in SurfSense. If you've been looking for **open source AI automation**, a **self-hosted AI agent** that runs on a schedule, or **document workflow automation** that reacts the instant a file shows up, this release is for you. Describe what you want in plain English, let SurfSense draft the automation, approve it, and your agent runs on its own: on a **cron schedule** or triggered by real events in your knowledge base. Best of all, automations work **across your connectors**, so one workflow can search and act on **Notion**, **Slack**, **Google Drive**, **Gmail**, **GitHub**, **Linear**, **Jira** and **Confluence**. This release also brings connector **@-mentions** into chat, a faster anonymous chat experience, and a redesigned homepage. + +### AI Automations + +Turn one-off prompts into repeatable, hands-off **AI agent workflows**. + +- **Build Automations From Chat**: Describe an automation in plain English and SurfSense drafts it for you. Review the generated workflow in a human-in-the-loop approval card, tweak it, and save. No config files, no code. +- **Scheduled AI Workflows**: Run an agent on a **cron schedule** for daily briefs, weekly digests, and recurring reports. SurfSense computes the next run time and fires it automatically in the background. +- **Event-Triggered Automations**: Kick off an agent the moment a document enters a folder. SurfSense's new in-process event bus watches your knowledge base and launches the right automation as soon as the event happens. +- **Agent Task Action**: Every automation runs a full multi-agent chat turn, so your scheduled and event-driven runs have the same reasoning, search, and tool access as a live chat, with an auto-approve loop so they finish unattended. +- **Automations Across Your Connectors**: Because each automation runs a full agent turn, your scheduled and event-triggered workflows can search and act across all 25+ connected sources, including **Notion**, **Slack**, **Google Drive**, **Gmail**, **GitHub**, **Linear**, **Jira**, **Confluence**, and your **Obsidian** vault. +- **Automations Dashboard**: A dedicated list view shows every automation with its status, plus one-click pause, resume, and delete. Find it in the sidebar under Inbox. +- **Detail & Run History**: Open any automation to inspect its definition, manage triggers inline, and browse a complete run history with inputs, outputs, and clear error reporting. +- **Power-User JSON Mode**: Create or edit an automation directly as raw JSON with a unified JSON viewer and editor for full control over the workflow definition. +- **Role-Based Access**: Automations are governed by SurfSense's RBAC system, with a dedicated permissions family so teams control exactly who can create, run, and manage them. + +### Connector Mentions in Chat + +- **@-Mention Your Connectors**: Mention connectors directly in the composer to scope a question to a specific source like **Notion**, **Slack**, **Google Drive**, **Gmail**, **GitHub**, **Linear**, **Jira**, or **Confluence**, alongside existing document mentions. +- **Recent Mentions**: SurfSense now remembers your recent mentions so the sources you use most are always one keystroke away. +- **Smoother Mention Picker**: A refreshed mention picker with loading skeletons, clearer connector definitions, and a better inline editing experience. + +### Faster, Friendlier Chat + +- **Anonymous Chat, Reworked**: The free, no-login chat experience has been rebuilt for a faster first impression and a cleaner anonymous-to-account path. +- **Better Long-Running Turns**: Improved task management and timeout handling in multi-agent chat keep complex, tool-heavy conversations reliable. +- **Leaner Toolset**: Retired the legacy in-product docs search tool to keep agent reasoning focused and fast. + +### Homepage & Marketing + +- **Redesigned Use-Case Showcase**: The homepage now groups demos into clear categories (Desktop App, Deliverable Studio, Search & Chat, Connectors & Integrations, and Automations) so visitors immediately see what SurfSense can do. +- **Desktop App, Front and Center**: The desktop experience is highlighted as a set of native extras on top of everything SurfSense already does, not a separate product. + + + + Bug Fixes + +
    +
  • Bulk-moving documents now uses ORM objects so folder events fire correctly and trigger automations
  • +
  • Automation enum columns now persist Postgres enum values instead of names
  • +
  • Automation agent tasks use an in-memory checkpointer to avoid Celery pool timeouts
  • +
  • The API client now handles 204 No Content responses without errors
  • +
  • The model role manager now stays in sync when preferences are updated
  • +
  • The JSON editor now coerces numeric strings to numbers on edit
  • +
+
+
+ + Under the Hood + +
    +
  • New in-process domain event bus with an event catalog and a document.entered_folder event
  • +
  • SQLAlchemy session hooks publish folder events automatically, registered at app startup
  • +
  • Cron schedule triggers backed by croniter and a Celery beat tick task
  • +
  • Sandboxed template engine with an allowlisted filter and test set for safe workflow templating
  • +
  • Automations reorganized into a vertical-slice architecture (actions and triggers grouped by domain)
  • +
  • Extensive new test coverage locking automation schemas, dispatch, runtime, triggers, and templating
  • +
  • Model eligibility checks when creating automations, so only valid models are selectable
  • +
+
+
+
+ +v0.0.26 turns SurfSense from a place you ask questions into a place that does the work for you. Whether you want **scheduled AI workflows**, **event-driven document automation**, or a **self-hosted, open source AI agent** you fully control, this release lets you build it from a single sentence. + +SurfSense connects all your knowledge sources in one place. diff --git a/surfsense_web/package.json b/surfsense_web/package.json index 6ac32160b..cc55d0d5d 100644 --- a/surfsense_web/package.json +++ b/surfsense_web/package.json @@ -1,6 +1,6 @@ { "name": "surfsense_web", - "version": "0.0.25", + "version": "0.0.26", "private": true, "packageManager": "pnpm@10.26.0", "description": "SurfSense Frontend", From 792d8520a619a0e5ddcbaa7a2a9f5dd2aa252ad2 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:25:14 -0700 Subject: [PATCH 128/133] feat(hero-section): enhance use case descriptions and add examples - Updated the hero section to include detailed examples for various use cases, improving user guidance. - Modified existing use case descriptions for clarity and relevance. - Introduced a new component to display example prompts, enhancing the overall user experience. --- .../components/homepage/hero-section.tsx | 80 +++++++++++++++++-- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index 8a831d492..0641d4e4e 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -1,5 +1,13 @@ "use client"; -import { ChevronDown, Clock, Download, Monitor, Sparkles } from "lucide-react"; +import { + ChevronDown, + Clock, + CornerDownLeft, + Download, + Lightbulb, + Monitor, + Sparkles, +} from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useRef, useState } from "react"; @@ -67,6 +75,7 @@ type HeroUseCase = { description: string; src: string | null; comingSoon?: boolean; + examples?: string[]; }; type HeroCategory = { @@ -143,9 +152,16 @@ const CATEGORIES: HeroCategory[] = [ { id: "resume", title: "AI Resume Builder", - description: "Draft and format an ATS-ready resume as a polished PDF.", + description: "Tailor your existing resume to any job description and beat the ATS.", src: null, comingSoon: true, + examples: [ + "Tailor my resume to this job description so it gets past ATS and lands an interview.", + "Optimize my resume for ATS by matching the keywords in this job posting.", + "Rewrite my resume bullet points to highlight the skills this role is asking for.", + "Compare my resume against this job description and list the gaps to fix.", + "Write a matching cover letter from my resume and this job description.", + ], }, ], }, @@ -159,6 +175,13 @@ const CATEGORIES: HeroCategory[] = [ description: "Run an agent on a schedule: daily briefs, weekly digests, recurring reports.", src: null, comingSoon: true, + examples: [ + "Email me a daily brief of new documents in my knowledge base every morning.", + "Generate a weekly status report from my Slack and Gmail every Friday.", + "Run a monthly competitor analysis report and save it to my workspace.", + "Summarize my GitHub and Linear activity into a daily standup update.", + "Create a recurring weekly research report on the topics I track.", + ], }, { id: "event", @@ -167,6 +190,13 @@ const CATEGORIES: HeroCategory[] = [ "Fire an agent the moment a document lands in a folder, then post the result to your tools.", src: null, comingSoon: true, + examples: [ + "When a PDF lands in my Research folder, generate a cited AI summary.", + "When new meeting notes are added, turn them into meeting minutes with action items.", + "When an invoice is uploaded, extract the vendor, total, and due date into a table.", + "When a contract enters my Legal folder, flag key terms and renewal dates.", + "When a resume is added to Candidates, screen it against the job description.", + ], }, { id: "chat-built", @@ -174,6 +204,13 @@ const CATEGORIES: HeroCategory[] = [ description: "Describe an automation in plain English and SurfSense builds it for you.", src: null, comingSoon: true, + examples: [ + "Build an AI agent that emails me a summary of new Notion pages each morning.", + "Create a no-code automation that posts a weekly research digest to Slack.", + "Set up an AI note taker that turns new meeting notes into minutes.", + "Make a workflow that extracts action items from meeting notes and assigns owners.", + "Automate a daily email brief from my Gmail and Google Drive.", + ], }, ], }, @@ -230,6 +267,13 @@ const CATEGORIES: HeroCategory[] = [ description: "Let the agent post results back to Notion, Slack, Linear and Drive.", src: null, comingSoon: true, + examples: [ + "Post this research summary to my Notion workspace.", + "Send these meeting action items to our team Slack channel.", + "Create a Jira ticket from this bug report.", + "Open a Linear issue from this feature request.", + "Save this generated report to Google Drive as a doc.", + ], }, ], }, @@ -435,6 +479,28 @@ const UseCasePlaceholder = ({ title }: { title: string }) => ( ); +const UseCaseExamples = ({ examples }: { examples: string[] }) => ( +
+
+
+
    + {examples.map((example) => ( +
  • +
    +
    +
  • + ))} +
+
+); + const DesktopBadge = () => ( @@ -469,9 +535,13 @@ const UseCasePane = memo(function UseCasePane({ ) : (
-
- -
+ {useCase.examples && useCase.examples.length > 0 ? ( + + ) : ( +
+ +
+ )}
); From 414ca6787234c27d055d03cbf9cf80615f883956 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:32:28 -0700 Subject: [PATCH 129/133] feat(docs): update README files with enhanced prompts - Revised the AI Resume Builder description to emphasize tailoring resumes for job descriptions and ATS compliance. - Added example prompts across multiple languages to guide users in utilizing the feature effectively. - Enhanced the automations section with new examples for various use cases, improving user engagement and understanding. --- README.es.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.hi.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.pt-BR.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.zh-CN.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 210 insertions(+), 30 deletions(-) diff --git a/README.es.md b/README.es.md index 75f499ab0..e7b61dd07 100644 --- a/README.es.md +++ b/README.es.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: redacta y da formato a un currículum listo para ATS en un PDF pulido. (próximamente) + - AI Resume Builder: adapta tu currículum existente a cualquier descripción de empleo y supera el ATS. + Prueba indicaciones como estas: + + - "Adapta mi currículum a esta descripción de empleo para superar el ATS y conseguir una entrevista." + - "Optimiza mi currículum para ATS haciendo coincidir las palabras clave de esta oferta." + - "Reescribe los puntos de mi currículum para resaltar las habilidades que pide este puesto." + - "Compara mi currículum con esta descripción de empleo y enumera las carencias a corregir." + - "Escribe una carta de presentación a juego con mi currículum y esta descripción de empleo." **Búsqueda y Chat** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: deja que el agente publique los resultados de vuelta en Notion, Slack, Linear y Drive. (próximamente) + - Connector Write-Back: deja que el agente publique los resultados de vuelta en Notion, Slack, Linear y Drive. + Prueba indicaciones como estas: - - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. (próximamente) + - "Publica este resumen de investigación en mi espacio de Notion." + - "Envía estos elementos de acción de la reunión a nuestro canal de Slack." + - "Crea un ticket de Jira a partir de este informe de error." + - "Abre una incidencia en Linear a partir de esta solicitud de función." + - "Guarda este informe generado en Google Drive como un documento." + - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. **Automatizaciones** - - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. (próximamente) - - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. (próximamente) - - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. (próximamente) + - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. + Prueba indicaciones como estas: + + - "Envíame cada mañana un resumen diario de los nuevos documentos en mi base de conocimiento." + - "Genera un informe de estado semanal a partir de mi Slack y Gmail cada viernes." + - "Ejecuta un informe mensual de análisis de la competencia y guárdalo en mi espacio de trabajo." + - "Resume mi actividad de GitHub y Linear en una actualización diaria de standup." + - "Crea un informe de investigación semanal recurrente sobre los temas que sigo." + + - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. + Prueba indicaciones como estas: + + - "Cuando llegue un PDF a mi carpeta de Investigación, genera un resumen de IA con citas." + - "Cuando se añadan nuevas notas de reunión, conviértelas en actas con elementos de acción." + - "Cuando se suba una factura, extrae el proveedor, el total y la fecha de vencimiento en una tabla." + - "Cuando entre un contrato en mi carpeta Legal, señala los términos clave y las fechas de renovación." + - "Cuando se añada un currículum a Candidatos, evalúalo frente a la descripción del empleo." + + - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. + Prueba indicaciones como estas: + + - "Crea un agente de IA que me envíe cada mañana un resumen de las nuevas páginas de Notion." + - "Crea una automatización sin código que publique un resumen de investigación semanal en Slack." + - "Configura un tomador de notas con IA que convierta las nuevas notas de reunión en actas." + - "Crea un flujo que extraiga los elementos de acción de las notas de reunión y asigne responsables." + - "Automatiza un resumen diario por correo a partir de mi Gmail y Google Drive." ### Auto-Hospedado diff --git a/README.hi.md b/README.hi.md index c03e8a21c..99bb1c25c 100644 --- a/README.hi.md +++ b/README.hi.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: ATS-तैयार रिज़्यूमे को एक बेहतरीन PDF के रूप में बनाएं और फ़ॉर्मेट करें। (जल्द आ रहा है) + - AI Resume Builder: अपने मौजूदा रिज़्यूमे को किसी भी जॉब डिस्क्रिप्शन के अनुसार ढालें और ATS को पार करें। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "मेरे रिज़्यूमे को इस जॉब डिस्क्रिप्शन के अनुसार ढालें ताकि वह ATS पार करे और इंटरव्यू दिलाए।" + - "इस जॉब पोस्टिंग के कीवर्ड्स से मिलान करके मेरे रिज़्यूमे को ATS के लिए ऑप्टिमाइज़ करें।" + - "इस भूमिका के लिए ज़रूरी स्किल्स को उभारने के लिए मेरे रिज़्यूमे के बुलेट पॉइंट फिर से लिखें।" + - "मेरे रिज़्यूमे की तुलना इस जॉब डिस्क्रिप्शन से करें और सुधारने योग्य कमियों की सूची दें।" + - "मेरे रिज़्यूमे और इस जॉब डिस्क्रिप्शन से मेल खाता एक कवर लेटर लिखें।" **सर्च और चैट** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: एजेंट को परिणाम वापस Notion, Slack, Linear और Drive में पोस्ट करने दें। (जल्द आ रहा है) + - Connector Write-Back: एजेंट को परिणाम वापस Notion, Slack, Linear और Drive में पोस्ट करने दें। + इस तरह के प्रॉम्प्ट आज़माएं: - - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। (जल्द आ रहा है) + - "इस रिसर्च सारांश को मेरे Notion वर्कस्पेस में पोस्ट करें।" + - "इन मीटिंग एक्शन आइटम्स को हमारे टीम Slack चैनल पर भेजें।" + - "इस बग रिपोर्ट से एक Jira टिकट बनाएं।" + - "इस फ़ीचर अनुरोध से Linear में एक इश्यू खोलें।" + - "इस जनरेट की गई रिपोर्ट को Google Drive में एक डॉक के रूप में सेव करें।" + - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। **ऑटोमेशन** - - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। (जल्द आ रहा है) - - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। (जल्द आ रहा है) - - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। (जल्द आ रहा है) + - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "हर सुबह मेरे नॉलेज बेस में जुड़े नए दस्तावेज़ों का रोज़ाना ब्रीफ़ मुझे ईमेल करें।" + - "हर शुक्रवार मेरे Slack और Gmail से एक साप्ताहिक स्टेटस रिपोर्ट बनाएं।" + - "एक मासिक प्रतिस्पर्धी विश्लेषण रिपोर्ट चलाएं और उसे मेरे वर्कस्पेस में सेव करें।" + - "मेरी GitHub और Linear गतिविधि को एक रोज़ाना standup अपडेट में सारांशित करें।" + - "मैं जिन विषयों को ट्रैक करता हूं उन पर एक आवर्ती साप्ताहिक रिसर्च रिपोर्ट बनाएं।" + + - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "जब मेरे Research फ़ोल्डर में कोई PDF आए, तो उद्धरण सहित एक AI सारांश बनाएं।" + - "जब नई मीटिंग नोट्स जुड़ें, तो उन्हें एक्शन आइटम्स के साथ मीटिंग मिनट्स में बदलें।" + - "जब कोई इनवॉइस अपलोड हो, तो विक्रेता, कुल राशि और देय तिथि को एक तालिका में निकालें।" + - "जब मेरे Legal फ़ोल्डर में कोई अनुबंध आए, तो मुख्य शर्तों और नवीनीकरण तिथियों को चिह्नित करें।" + - "जब Candidates में कोई रिज़्यूमे जुड़े, तो उसे जॉब डिस्क्रिप्शन के विरुद्ध स्क्रीन करें।" + + - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "एक AI एजेंट बनाएं जो हर सुबह नई Notion पेजों का सारांश मुझे ईमेल करे।" + - "एक नो-कोड ऑटोमेशन बनाएं जो हर सप्ताह एक रिसर्च डाइजेस्ट Slack पर पोस्ट करे।" + - "एक AI नोट-टेकर सेट करें जो नई मीटिंग नोट्स को मिनट्स में बदल दे।" + - "एक वर्कफ़्लो बनाएं जो मीटिंग नोट्स से एक्शन आइटम्स निकाले और ज़िम्मेदार सौंपे।" + - "मेरे Gmail और Google Drive से एक रोज़ाना ईमेल ब्रीफ़ को ऑटोमेट करें।" ### सेल्फ-होस्टेड diff --git a/README.md b/README.md index 93ad51263..25a28c9f7 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: draft and format an ATS-ready resume as a polished PDF. (coming soon) + - AI Resume Builder: tailor your existing resume to any job description and beat the ATS. + Try prompts like these: + + - "Tailor my resume to this job description so it gets past ATS and lands an interview." + - "Optimize my resume for ATS by matching the keywords in this job posting." + - "Rewrite my resume bullet points to highlight the skills this role is asking for." + - "Compare my resume against this job description and list the gaps to fix." + - "Write a matching cover letter from my resume and this job description." **Search & Chat** @@ -143,15 +150,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: let the agent post results back to Notion, Slack, Linear and Drive. (coming soon) + - Connector Write-Back: let the agent post results back to Notion, Slack, Linear and Drive. + Try prompts like these: - - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. (coming soon) + - "Post this research summary to my Notion workspace." + - "Send these meeting action items to our team Slack channel." + - "Create a Jira ticket from this bug report." + - "Open a Linear issue from this feature request." + - "Save this generated report to Google Drive as a doc." + - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. **Automations** - - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. (coming soon) - - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. (coming soon) - - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. (coming soon) + - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. + Try prompts like these: + + - "Email me a daily brief of new documents in my knowledge base every morning." + - "Generate a weekly status report from my Slack and Gmail every Friday." + - "Run a monthly competitor analysis report and save it to my workspace." + - "Summarize my GitHub and Linear activity into a daily standup update." + - "Create a recurring weekly research report on the topics I track." + + - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. + Try prompts like these: + + - "When a PDF lands in my Research folder, generate a cited AI summary." + - "When new meeting notes are added, turn them into meeting minutes with action items." + - "When an invoice is uploaded, extract the vendor, total, and due date into a table." + - "When a contract enters my Legal folder, flag key terms and renewal dates." + - "When a resume is added to Candidates, screen it against the job description." + + - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. + Try prompts like these: + + - "Build an AI agent that emails me a summary of new Notion pages each morning." + - "Create a no-code automation that posts a weekly research digest to Slack." + - "Set up an AI note taker that turns new meeting notes into minutes." + - "Make a workflow that extracts action items from meeting notes and assigns owners." + - "Automate a daily email brief from my Gmail and Google Drive." ### Self Hosted diff --git a/README.pt-BR.md b/README.pt-BR.md index e7112bcaa..61d08afaa 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: monte e formate um currículo pronto para ATS em um PDF caprichado. (em breve) + - AI Resume Builder: adapte seu currículo atual a qualquer descrição de vaga e supere o ATS. + Experimente prompts como estes: + + - "Adapte meu currículo a esta descrição de vaga para passar pelo ATS e conseguir uma entrevista." + - "Otimize meu currículo para o ATS combinando as palavras-chave desta vaga." + - "Reescreva os tópicos do meu currículo para destacar as habilidades que esta vaga exige." + - "Compare meu currículo com esta descrição de vaga e liste as lacunas a corrigir." + - "Escreva uma carta de apresentação combinando com meu currículo e esta descrição de vaga." **Busca e Chat** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: deixe o agente publicar os resultados de volta no Notion, Slack, Linear e Drive. (em breve) + - Connector Write-Back: deixe o agente publicar os resultados de volta no Notion, Slack, Linear e Drive. + Experimente prompts como estes: - - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. (em breve) + - "Publique este resumo de pesquisa no meu espaço do Notion." + - "Envie estes itens de ação da reunião para o nosso canal do Slack." + - "Crie um ticket no Jira a partir deste relatório de bug." + - "Abra uma issue no Linear a partir desta solicitação de funcionalidade." + - "Salve este relatório gerado no Google Drive como um documento." + - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. **Automações** - - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. (em breve) - - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. (em breve) - - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. (em breve) + - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. + Experimente prompts como estes: + + - "Envie-me todas as manhãs um resumo diário dos novos documentos na minha base de conhecimento." + - "Gere um relatório de status semanal a partir do meu Slack e Gmail toda sexta-feira." + - "Execute um relatório mensal de análise da concorrência e salve-o no meu espaço de trabalho." + - "Resuma minha atividade no GitHub e Linear em uma atualização diária de standup." + - "Crie um relatório de pesquisa semanal recorrente sobre os temas que acompanho." + + - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. + Experimente prompts como estes: + + - "Quando um PDF chegar à minha pasta de Pesquisa, gere um resumo com IA e citações." + - "Quando novas notas de reunião forem adicionadas, transforme-as em atas com itens de ação." + - "Quando uma fatura for enviada, extraia o fornecedor, o total e a data de vencimento em uma tabela." + - "Quando um contrato entrar na minha pasta Jurídica, sinalize os termos-chave e as datas de renovação." + - "Quando um currículo for adicionado a Candidatos, avalie-o em relação à descrição da vaga." + + - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. + Experimente prompts como estes: + + - "Crie um agente de IA que me envie todas as manhãs um resumo das novas páginas do Notion." + - "Crie uma automação sem código que publique um resumo de pesquisa semanal no Slack." + - "Configure um anotador com IA que transforme as novas notas de reunião em atas." + - "Crie um fluxo que extraia os itens de ação das notas de reunião e atribua responsáveis." + - "Automatize um resumo diário por e-mail a partir do meu Gmail e Google Drive." ### Auto-Hospedado diff --git a/README.zh-CN.md b/README.zh-CN.md index cc73fbe6a..4548456e6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder:撰写并排版一份适配 ATS 的精美 PDF 简历。(即将推出) + - AI Resume Builder:根据任意职位描述定制你现有的简历,顺利通过 ATS。 + 可以试试这样的提示: + + - “根据这份职位描述定制我的简历,让它通过 ATS 并赢得面试。” + - “匹配这份招聘启事中的关键词,为 ATS 优化我的简历。” + - “重写我的简历要点,突出这个岗位所需要的技能。” + - “将我的简历与这份职位描述对比,列出需要改进的差距。” + - “根据我的简历和这份职位描述,写一封相匹配的求职信。” **搜索与聊天** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back:让智能体将结果回写到 Notion、Slack、Linear 和 Drive。(即将推出) + - Connector Write-Back:让智能体将结果回写到 Notion、Slack、Linear 和 Drive。 + 可以试试这样的提示: - - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。(即将推出) + - “把这份研究摘要发布到我的 Notion 工作区。” + - “把这些会议行动项发送到我们的团队 Slack 频道。” + - “根据这份缺陷报告创建一个 Jira 工单。” + - “根据这个功能需求在 Linear 中创建一个 issue。” + - “把这份生成的报告作为文档保存到 Google Drive。” + - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。 **自动化** - - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。(即将推出) - - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。(即将推出) - - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。(即将推出) + - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。 + 可以试试这样的提示: + + - “每天早上把我知识库中新增文档的每日简报发邮件给我。” + - “每周五根据我的 Slack 和 Gmail 生成一份每周状态报告。” + - “每月运行一次竞争对手分析报告并保存到我的工作区。” + - “把我的 GitHub 和 Linear 活动汇总成一份每日站会更新。” + - “针对我关注的主题创建一份周期性的每周研究报告。” + + - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。 + 可以试试这样的提示: + + - “当一个 PDF 进入我的 Research 文件夹时,生成一份带引用的 AI 摘要。” + - “当新增会议记录时,把它整理成带行动项的会议纪要。” + - “当上传发票时,把供应商、总额和到期日提取到一张表格中。” + - “当一份合同进入我的 Legal 文件夹时,标记关键条款和续约日期。” + - “当一份简历加入 Candidates 时,根据职位描述对其进行筛选。” + + - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。 + 可以试试这样的提示: + + - “创建一个 AI 智能体,每天早上把新增 Notion 页面的摘要发邮件给我。” + - “创建一个无代码自动化,每周把研究摘要发布到 Slack。” + - “设置一个 AI 笔记助手,把新增会议记录整理成纪要。” + - “创建一个工作流,从会议记录中提取行动项并指派负责人。” + - “自动化一份来自我的 Gmail 和 Google Drive 的每日邮件简报。” ### 自托管 From 0ae30839aaf35dda31969698fdc7a4653ca7ce42 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:36:57 -0700 Subject: [PATCH 130/133] feat(docs): add AI automations and agents section to README files - Introduced a new feature highlighting AI automations and agents across multiple language README files. - Described capabilities for scheduling AI workflows and event-triggered actions, enhancing user understanding of automation functionalities. - Updated feature comparison tables to include AI automations, providing a clearer overview of available features. --- README.es.md | 2 ++ README.hi.md | 2 ++ README.md | 2 ++ README.pt-BR.md | 2 ++ README.zh-CN.md | 2 ++ 5 files changed, 10 insertions(+) diff --git a/README.es.md b/README.es.md index e7b61dd07..ffd4f5e5b 100644 --- a/README.es.md +++ b/README.es.md @@ -41,6 +41,7 @@ NotebookLM es una de las mejores y más útiles plataformas de IA que existen, p - **Sin Dependencia de Proveedores** - Configura cualquier modelo LLM, de imagen, TTS y STT. - **25+ Fuentes de Datos Externas** - Agrega tus fuentes desde Google Drive, OneDrive, Dropbox, Notion y muchos otros servicios externos. - **Soporte Multijugador en Tiempo Real** - Trabaja fácilmente con los miembros de tu equipo en un notebook compartido. +- **Automatizaciones y Agentes de IA** - Ejecuta agentes de IA según una programación o actívalos en el momento en que un documento llega a una carpeta, y luego escribe los resultados de vuelta en Notion, Slack, Linear y Drive. Crea automatizaciones sin código solo describiéndolas en el chat. - **Aplicación de Escritorio** - Obtén asistencia de IA en cualquier aplicación con Quick Assist, General Assist, Screenshot Assist y sincronización de carpetas locales. ...y más por venir. @@ -268,6 +269,7 @@ Todas las funciones operan contra tu espacio de búsqueda elegido, por lo que tu | **Generación de Videos** | Resúmenes en video cinemáticos vía Veo 3 (solo Ultra) | Disponible (NotebookLM es mejor aquí, mejorando activamente) | | **Generación de Presentaciones** | Diapositivas más atractivas pero no editables | Crea presentaciones editables basadas en diapositivas | | **Generación de Podcasts** | Resúmenes de audio con hosts e idiomas personalizables | Disponible con múltiples proveedores TTS (NotebookLM es mejor aquí, mejorando activamente) | +| **Automatizaciones y Agentes de IA** | No | Flujos de trabajo de IA programados, disparadores por eventos en documentos nuevos y automatizaciones sin código creadas por chat con escritura de vuelta a Notion, Slack, Linear y Jira | | **Aplicación de Escritorio** | No | Aplicación nativa con General Assist, Quick Assist, Screenshot Assist y sincronización de carpetas locales | | **Extensión de Navegador** | No | Extensión multi-navegador para guardar cualquier página web, incluyendo páginas protegidas por autenticación | diff --git a/README.hi.md b/README.hi.md index 99bb1c25c..b12842f9c 100644 --- a/README.hi.md +++ b/README.hi.md @@ -41,6 +41,7 @@ NotebookLM वहाँ उपलब्ध सबसे अच्छे और - **कोई विक्रेता लॉक-इन नहीं** - किसी भी LLM, इमेज, TTS और STT मॉडल को कॉन्फ़िगर करें। - **25+ बाहरी डेटा स्रोत** - Google Drive, OneDrive, Dropbox, Notion और कई अन्य बाहरी सेवाओं से अपने स्रोत जोड़ें। - **रीयल-टाइम मल्टीप्लेयर सपोर्ट** - एक साझा notebook में अपनी टीम के सदस्यों के साथ आसानी से काम करें। +- **AI ऑटोमेशन और एजेंट** - AI एजेंट को शेड्यूल पर चलाएं या जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आए उसे ट्रिगर करें, फिर परिणाम वापस Notion, Slack, Linear और Drive में लिखें। चैट में बस वर्णन करके बिना-कोड ऑटोमेशन बनाएं। - **डेस्कटॉप ऐप** - Quick Assist, General Assist, Screenshot Assist और लोकल फ़ोल्डर सिंक के साथ किसी भी एप्लिकेशन में AI सहायता प्राप्त करें। ...और भी बहुत कुछ आने वाला है। @@ -268,6 +269,7 @@ SurfSense एक डेस्कटॉप ऐप भी प्रदान क | **वीडियो जनरेशन** | Veo 3 के माध्यम से सिनेमैटिक वीडियो ओवरव्यू (केवल Ultra) | उपलब्ध (NotebookLM यहाँ बेहतर है, सक्रिय रूप से सुधार हो रहा है) | | **प्रेजेंटेशन जनरेशन** | बेहतर दिखने वाली स्लाइड्स लेकिन संपादन योग्य नहीं | संपादन योग्य, स्लाइड आधारित प्रेजेंटेशन बनाएं | | **पॉडकास्ट जनरेशन** | कस्टमाइज़ेबल होस्ट और भाषाओं के साथ ऑडियो ओवरव्यू | कई TTS प्रदाताओं के साथ उपलब्ध (NotebookLM यहाँ बेहतर है, सक्रिय रूप से सुधार हो रहा है) | +| **AI ऑटोमेशन और एजेंट** | नहीं | शेड्यूल किए गए AI वर्कफ़्लो, नए दस्तावेज़ों पर इवेंट ट्रिगर, और चैट से बने बिना-कोड ऑटोमेशन, Notion, Slack, Linear और Jira में कनेक्टर राइट-बैक के साथ | | **डेस्कटॉप ऐप** | नहीं | General Assist, Quick Assist, Screenshot Assist और लोकल फ़ोल्डर सिंक के साथ नेटिव ऐप | | **ब्राउज़र एक्सटेंशन** | नहीं | किसी भी वेबपेज को सहेजने के लिए क्रॉस-ब्राउज़र एक्सटेंशन, प्रमाणीकरण सुरक्षित पेज सहित | diff --git a/README.md b/README.md index 25a28c9f7..d9beb5da4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ NotebookLM is one of the best and most useful AI platforms out there, but once y - **25+ External Data Sources** - Add your sources from Google Drive, OneDrive, Dropbox, Notion, and many other external services. - **Real-Time Multiplayer Support** - Work easily with your team members in a shared notebook. - **AI File Sorting** - Automatically organize your documents into a smart folder hierarchy using AI-powered categorization by source, date, and topic. +- **AI Automations & Agents** - Run AI agents on a schedule or trigger them the moment a document lands in a folder, then write results back to Notion, Slack, Linear, and Drive. Build no-code automations just by describing them in chat. - **Desktop App** - Get AI assistance in any application with Quick Assist, General Assist, Screenshot Assist, and local folder sync. ...and more to come. @@ -270,6 +271,7 @@ All features operate against your chosen search space, so your answers are alway | **Presentation Generation** | Better looking slides but not editable | Create editable, slide-based presentations | | **Podcast Generation** | Audio Overviews with customizable hosts and languages | Available with multiple TTS providers (NotebookLM is better here, actively improving) | | **AI File Sorting** | No | LLM-powered auto-categorization into source, date, category, and subcategory folders | +| **AI Automations & Agents** | No | Scheduled AI workflows, event triggers on new documents, and chat-built no-code automations with connector write-back to Notion, Slack, Linear & Jira | | **Desktop App** | No | Native app with General Assist, Quick Assist, Screenshot Assist, and local folder sync | | **Browser Extension** | No | Cross-browser extension to save any webpage, including auth-protected pages | diff --git a/README.pt-BR.md b/README.pt-BR.md index 61d08afaa..1b8c6f332 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -41,6 +41,7 @@ O NotebookLM é uma das melhores e mais úteis plataformas de IA disponíveis, m - **Sem Dependência de Fornecedor** - Configure qualquer modelo LLM, de imagem, TTS e STT. - **25+ Fontes de Dados Externas** - Adicione suas fontes do Google Drive, OneDrive, Dropbox, Notion e muitos outros serviços externos. - **Suporte Multiplayer em Tempo Real** - Trabalhe facilmente com os membros da sua equipe em um notebook compartilhado. +- **Automações e Agentes de IA** - Execute agentes de IA em uma programação ou dispare-os no momento em que um documento chega a uma pasta, e escreva os resultados de volta no Notion, Slack, Linear e Drive. Crie automações sem código apenas descrevendo-as no chat. - **Aplicativo Desktop** - Obtenha assistência de IA em qualquer aplicativo com Quick Assist, General Assist, Screenshot Assist e sincronização de pastas locais. ...e mais por vir. @@ -268,6 +269,7 @@ Todos os recursos operam no espaço de busca escolhido, para que suas respostas | **Geração de Vídeos** | Visões gerais cinemáticas via Veo 3 (apenas Ultra) | Disponível (NotebookLM é melhor aqui, melhorando ativamente) | | **Geração de Apresentações** | Slides mais bonitos mas não editáveis | Cria apresentações editáveis baseadas em slides | | **Geração de Podcasts** | Visões gerais em áudio com hosts e idiomas personalizáveis | Disponível com múltiplos provedores TTS (NotebookLM é melhor aqui, melhorando ativamente) | +| **Automações e Agentes de IA** | Não | Fluxos de trabalho de IA agendados, gatilhos por eventos em novos documentos e automações sem código criadas por chat com escrita de volta no Notion, Slack, Linear e Jira | | **Aplicativo Desktop** | Não | Aplicativo nativo com General Assist, Quick Assist, Screenshot Assist e sincronização de pastas locais | | **Extensão de Navegador** | Não | Extensão multi-navegador para salvar qualquer página web, incluindo páginas protegidas por autenticação | diff --git a/README.zh-CN.md b/README.zh-CN.md index 4548456e6..131393c77 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -41,6 +41,7 @@ NotebookLM 是目前最好、最实用的 AI 平台之一,但当你开始经 - **无供应商锁定** - 配置任何 LLM、图像、TTS 和 STT 模型。 - **25+ 外部数据源** - 从 Google Drive、OneDrive、Dropbox、Notion 和许多其他外部服务添加你的来源。 - **实时多人协作支持** - 在共享笔记本中轻松与团队成员协作。 +- **AI 自动化与智能体** - 按计划运行 AI 智能体,或在文档进入文件夹的那一刻触发它们,然后将结果回写到 Notion、Slack、Linear 和 Drive。只需在聊天中描述即可创建无代码自动化。 - **桌面应用** - 通过 Quick Assist、General Assist、Screenshot Assist 和本地文件夹同步在任何应用程序中获得 AI 助手。 ...更多功能即将推出。 @@ -268,6 +269,7 @@ SurfSense 还提供桌面应用,将 AI 助手带到您计算机上的每个应 | **视频生成** | 通过 Veo 3 的电影级视频概览(仅 Ultra) | 可用(NotebookLM 在此方面更好,正在积极改进) | | **演示文稿生成** | 更美观的幻灯片但不可编辑 | 创建可编辑的幻灯片式演示文稿 | | **播客生成** | 可自定义主持人和语言的音频概览 | 可用,支持多种 TTS 提供商(NotebookLM 在此方面更好,正在积极改进) | +| **AI 自动化与智能体** | 否 | 定时 AI 工作流、新文档的事件触发,以及通过聊天构建的无代码自动化,支持回写到 Notion、Slack、Linear 和 Jira | | **桌面应用** | 否 | 原生应用,包含 General Assist、Quick Assist、Screenshot Assist 和本地文件夹同步 | | **浏览器扩展** | 否 | 跨浏览器扩展,保存任何网页,包括需要身份验证的页面 | From ec0342faa27e60c7d90802d6b2b071fa0d6d2e41 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:51:49 -0700 Subject: [PATCH 131/133] feat(announcements): enhance AnnouncementCard and add spotlight feature - Added image support to the AnnouncementCard component for improved visual presentation of announcements. - Introduced a spotlight feature in the announcement types to allow critical announcements to be displayed in a blocking dialog until acknowledged. - Updated AnnouncementToastProvider to skip spotlight announcements to prevent duplicate notifications. - Included a new AI automation announcement with an image in the announcements data for demonstration purposes. --- .../announcements/AnnouncementCard.tsx | 14 ++- .../announcements/AnnouncementSpotlight.tsx | 101 ++++++++++++++++++ .../AnnouncementToastProvider.tsx | 4 +- .../layout/providers/LayoutDataProvider.tsx | 2 + .../contracts/types/announcement.types.ts | 14 +++ .../lib/announcements/announcements-data.ts | 21 ++++ .../public/announcements/automations.png | Bin 0 -> 4420756 bytes 7 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 surfsense_web/components/announcements/AnnouncementSpotlight.tsx create mode 100644 surfsense_web/public/announcements/automations.png diff --git a/surfsense_web/components/announcements/AnnouncementCard.tsx b/surfsense_web/components/announcements/AnnouncementCard.tsx index ea0288b43..83a0e09b8 100644 --- a/surfsense_web/components/announcements/AnnouncementCard.tsx +++ b/surfsense_web/components/announcements/AnnouncementCard.tsx @@ -1,6 +1,7 @@ "use client"; import { Bell, ExternalLink, Info, type LucideIcon, Rocket, Wrench, Zap } from "lucide-react"; +import Image from "next/image"; import Link from "next/link"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -49,7 +50,18 @@ export function AnnouncementCard({ announcement }: { announcement: AnnouncementW const Icon = config.icon; return ( - + + {announcement.image && ( +
+ {announcement.image.alt} +
+ )}
diff --git a/surfsense_web/components/announcements/AnnouncementSpotlight.tsx b/surfsense_web/components/announcements/AnnouncementSpotlight.tsx new file mode 100644 index 000000000..794a3c1cf --- /dev/null +++ b/surfsense_web/components/announcements/AnnouncementSpotlight.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { ExternalLink } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogTitle, +} from "@/components/ui/dialog"; +import { useAnnouncements } from "@/hooks/use-announcements"; + +/** + * Proactively shows important "spotlight" announcements in a blocking dialog. + * + * Behaviour: + * - On load, the first active, audience-matched, unread spotlight announcement + * is shown automatically. + * - The user must explicitly acknowledge it ("Got it" or the CTA link), which + * marks it as read so it never shows again. + * - Closing via the X / Escape / outside-click only hides it for the current + * session; it reappears on the next load until the user marks it as seen. + */ +export function AnnouncementSpotlight() { + const { announcements, markRead } = useAnnouncements(); + const [sessionDismissed, setSessionDismissed] = useState>(() => new Set()); + const [ready, setReady] = useState(false); + + // Short delay so the spotlight doesn't flash during initial hydration/layout. + useEffect(() => { + const timer = setTimeout(() => setReady(true), 800); + return () => clearTimeout(timer); + }, []); + + const current = useMemo( + () => + announcements.find( + (a) => a.spotlight && a.isImportant && !a.isRead && !sessionDismissed.has(a.id) + ) ?? null, + [announcements, sessionDismissed] + ); + + if (!current) return null; + + const handleAcknowledge = () => { + markRead(current.id); + }; + + const handleOpenChange = (next: boolean) => { + if (!next) { + setSessionDismissed((prev) => { + const updated = new Set(prev); + updated.add(current.id); + return updated; + }); + } + }; + + return ( + + + {current.image && ( +
+ {current.image.alt} +
+ )} +
+ {current.title} + + {current.description} + + + {current.link && ( + + )} + + +
+
+
+ ); +} diff --git a/surfsense_web/components/announcements/AnnouncementToastProvider.tsx b/surfsense_web/components/announcements/AnnouncementToastProvider.tsx index 3e99f5c32..5578eba17 100644 --- a/surfsense_web/components/announcements/AnnouncementToastProvider.tsx +++ b/surfsense_web/components/announcements/AnnouncementToastProvider.tsx @@ -70,8 +70,10 @@ export function AnnouncementToastProvider() { const outerTimer = setTimeout(() => { const authed = isAuthenticated(); const active = getActiveAnnouncements(announcements, authed); + // Spotlight announcements are handled by the blocking spotlight dialog, + // so skip them here to avoid double-notifying the user. const importantUntoasted = active.filter( - (a) => a.isImportant && !isAnnouncementToasted(a.id) + (a) => a.isImportant && !a.spotlight && !isAnnouncementToasted(a.id) ); for (let i = 0; i < importantUntoasted.length; i++) { diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 4284e3da7..34fd15e3b 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -18,6 +18,7 @@ import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; import { ActionLogDialog } from "@/components/agent-action-log/action-log-dialog"; +import { AnnouncementSpotlight } from "@/components/announcements/AnnouncementSpotlight"; import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog"; import { AlertDialog, @@ -909,6 +910,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid /> + {/* Agent action log + revert dialog */} diff --git a/surfsense_web/contracts/types/announcement.types.ts b/surfsense_web/contracts/types/announcement.types.ts index 6c5206d9d..05cf14d21 100644 --- a/surfsense_web/contracts/types/announcement.types.ts +++ b/surfsense_web/contracts/types/announcement.types.ts @@ -35,6 +35,20 @@ export interface Announcement { audience: AnnouncementAudience; /** If true, the user will see a toast notification for this announcement */ isImportant: boolean; + /** + * If true, this announcement is shown in a blocking spotlight dialog that the + * user must explicitly acknowledge ("Got it"). Until acknowledged it keeps + * reappearing; once acknowledged it never shows again. Spotlight announcements + * are skipped by the lightweight toast provider to avoid double notifications. + */ + spotlight?: boolean; + /** Optional head/banner image shown at the top of the announcement */ + image?: { + /** Image source (public path or absolute URL) */ + src: string; + /** Accessible alt text */ + alt: string; + }; /** Optional CTA link */ link?: { label: string; diff --git a/surfsense_web/lib/announcements/announcements-data.ts b/surfsense_web/lib/announcements/announcements-data.ts index ce44ec539..e5f3f0fce 100644 --- a/surfsense_web/lib/announcements/announcements-data.ts +++ b/surfsense_web/lib/announcements/announcements-data.ts @@ -13,6 +13,27 @@ import type { Announcement } from "@/contracts/types/announcement.types"; * This file can be replaced with an API call in the future. */ export const announcements: Announcement[] = [ + { + id: "2026-05-31-ai-automations", + title: "Introducing AI Automations", + description: + "Turn prompts into hands-off AI agent workflows. Describe an automation in plain English and SurfSense builds it, run it on a schedule, or trigger it the moment a document lands in a folder. Automations work across Notion, Slack, Google Drive, Gmail, GitHub, Linear, Jira and more.", + category: "feature", + date: "2026-05-31T00:00:00Z", + startTime: "2026-05-31T00:00:00Z", + endTime: "2026-07-15T00:00:00Z", + audience: "users", + isImportant: true, + spotlight: true, + image: { + src: "/announcements/automations.png", + alt: "Connector tiles flowing into a central AI core that triggers scheduled and event-driven automations.", + }, + link: { + label: "See what's new", + url: "/changelog", + }, + }, { id: "announcement-1", title: "Introducing What's New", diff --git a/surfsense_web/public/announcements/automations.png b/surfsense_web/public/announcements/automations.png new file mode 100644 index 0000000000000000000000000000000000000000..39eb3583707763ab6b544aad7e76ed5253c1b397 GIT binary patch literal 4420756 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

9`~&$Rm8H3 zu&eu}6Db6DA*+L;;Tk7r?6Ib*qkc#p|=0 zS#Dbs9U~gZB-pU?;A7)$Y~q4b{PfY+;X z9(w!gOiw_QB@|&Ou>X&&ciFY2+p@F1uaCL&f{hKx7|B_24Ot+tge53o2_vw{B2R!Q z@{C+0Bx+FMArM%$N%>@+GqN`pS6_XXWz>#DtL^fQR zerW+$#2fcsg}~j-@1uXoTUi7S9$K6f|7MK0Ldk39214hXQ95$>@JnX-j$h>eCmyNp zGzbhK!`a;%)?PreL6X}n1{gk=*pLAfy9KEadgtNVz0#-zyuEcv|Ea zOh}ZJmxOm^qIPL4>UmecrYT2f?ph4tU58C27t&whwNNX-+Lfebqs`KA?cal7^|d25 z3fY<(D8kOXD{pB!Y_ymGUooH-Av3hzz}L^afCq5XuC_NqQMV7(i4ONBP^~z-+n|&L zlI-Z<*Yg;vy3pY(2LYKOyH#J=>+yneyIn0g0m=vNEUBe#=p=Unz&oQbcj9bU!yt{H zLbl_O2H0L(g;OU2AWh8BK$;|oBhzw`HfJ4KrS{rT|LDEO8(Xv6R^n9vl)Or9|3Iia zK;H{{`E-SxCk8eW5|xxOFQ2qQO>(V#+142~jj)$cV6qC{)1R*Rt`&X`NiT$KcanZImPbjg!rV3ZtDqZIZsL1kEoTu^1I4yBN71qyO+%0A1} zR0Y;c-s=Pu^;F70H`92jATX^nUyJQ-183>h9G$=xsV2c+L+*3q=ZywgfPIp%X>6ED zmz4;+w^IXh?KT}RW32<^M)HJyNBixmlU@~KN5ATA)Ibsty_&ske6d~Nuk>q=7K}wSE<5;JOPPJ@g z3IOwdrNX7<+-_H3X+p_MG4R=v7+)I$NwoEOR^rModtWk#Nv1FU_Z3WY%g;dZ&FOfEEYG1d_+TqVHW~wDG?#2?Z+mAAOrmfRMiL0U*9>gN~wn)R1r! zq9{$%&h^UL@FLcACW~YkMM@s-CpR%wkkKVcSJ;4n$mq7u>b#o?c6mDQKwNh9*Gpom zS*N3}aepq}i3aaXkl>8--A@bY|4FWM5%G1Xz_rVZC&?y->JF21s;*-V@S*KDS53%f zoQ3WPjL`&zr&ks_pqs~B;BqtsblmGO-wno%)X}|fbxN~WfDi1l>J*Y<#>wu^+=VM; z!$=Mx#&GFl4K*gc5ql9!L2cg3x78)9=ibRxzQMc-&xu(aOp@uEPR2>H zGPZ{l@Dj5bD8dbOo6H9iLh($#E=^97m;uW2Yv=(sJAmZbyNHb0ddN3o0Qe!Bca<%~ zy>8I39|A5m5}cfeFtdcL*#muGPMQHQbeqUUDsc#klj&%MHySC8a^p@ZD~&+4a|b2&jfF_aWJ8jR%57EUs(yt9Uh6k}F)U z96V^TE4(H!sW&c=b@b^byG)q06bfo$bb}5b)tDuB%fO`G2&s_1uY{)*z zkyt%>0Oy2=t@T^-25vuKr~ML{8nJ?b$+I{*!tTR-a08d95~C2H->wE21n+mGx&2ga zqrC2Tri*u@e0vIHq=CuHN9oJ_yI)5Ibh>UAhs+N?p3Xe#p3l$BZ@+%dj~{>YZ~e`G z=WqX8fArV>_~*}`|MFk_=Fk55KmYmL-~I9V@rR$2RYxUBW54pO$`7)cWiw}YJ}CKg2ZmG)0*=WN@LYhU zfn^}Z_&J|IQ!UbzgvbUPW@U0yb7H(Xv-aIQQA0R&hroew_k<={`GhxF6N3{V5e?LE z4}GX3v3pB5Km?XBWx)v9o%10;{qc+@8~Pxb=k^ke2KL!ERIH=@4zhc~9?#u=7y{!g zH#LMc(MI07(HKG;T1NCE1G#xQ-u9V~Bx}ELYlE&8THaA@rQ$w0 z6L{cb9dCD#XPXbxfZUR=@#h+I5VFo>vbjBL9u)O?G6S(8ow$kh<+_zaBO15t7|R zvIv>2(S#QLNfJHL+_#1rE`Etu`eg5y876La`n@s*!cAP{W&=nVKU^)q6OhN3<*)(w z#7SlDOayVJbDHdWX6X1uX0P1lh|Y8|1#d1Q$w|^Cz+ubH^y097WXJ3}t)3A2;Cu_u zIA+t@zVY!^$?`O# zCR@b?*;T-0lW0u(Dh|2T;9GZMcDBR?pk!lAkO{voO!6+|wk)26VAYMl(Wl+6!7Z!==1IUMs1-hzFl60HpXp&?Hk%ZywV^}0; zLoLLS$wg5JChW9-*;N{2+ghuw!=OcL|H|BaL4up|J9xbjpyoTMNxU|7~O?(99qsCd}GT9&pwh6Zw244w(s z3_7fAk#sielR4Axt}DrFD4mi`$#>Pb-GjNNNeDR*Z!U@nF>obAHY?oAmePb|lktRa zVw|IqB$G*QvF3MMTmgmI1U#0pTvU5QxAUGFl*_9X@Y#$s4ds?`%`v9cU-%Bo1)(0~ zW(V>-o{y6Vn@z;)`8oaw*qX43`Qmm+(xxj$F#r_{7{im%M*4o=r}lcLH%RkaEVQrT93S&2m)Ogf?hW z|CQv53^o&+D<_+*864bf;h_y=7A@OAEcZoeo1=E{P)7qk>Do1y_ z?@+tW;|BsJgtZm62oF1n9GJl`9yFYXJIRTJXChsE=ylAlGs`MF16RCf_jI)gakNUW zUy~2YF3!~d$D0O+M*dqD0ku{{YT%G`4XTPk`mi7irUUHejI-sGfNl~p zAoC=U>r3j6RU_oCxBCQ%GPidLdcuP@BZcTJC_E21tq6j_AsgM@=S`*Ct&?d#SMR6Z zUK{lsy6&XH7xwG77h*?THy zXLqp~(-wep{0fAoM>|^$GbWKq_BPu#7%zbGJbLdY-yl)GC2$a%@lM1f=BxL5@KNWS z1xZH({P6(|aiWQsbfLByl-24_`{mHD$Tmh?J1;g|+r2b{)2y`IZ&c6~aN5 zC7YJ$ER(MQ4nVV?X*xw7nh6Vzm30gPAeAt!nMxMD%Xz<+HBXEUb zF^9g(Bs!AK5_<%a*trAP+fz24ZRZ6mIb~?89T@IsMGh00naQrUp0s}gn40I=lueNF zaPgl^$O?zEV)ijJpWVe=U1bH=`xxI|q#_6{?$|7K!F*fdvpk*QNz}g46+YR7FHaY3 z3M%i0jO=CtV|W@oZ9rcPVP}%^t%?R4IrGeGe!7HnfUTuTL;eQ5b2kQ_h-}@lsZDtp z$dP1$kXuv41lp_SavMvJPJX#}1BoZVHF!u`CtP`|e7@teJ!48-aPHll=7LWr=PKUc z62`=XPl@I`C4_(tTaw*m?pzI4p^QpE-V7okZGjRSeedz4^&NRf=Pm-Ww(4vYY`DCl z-f=2g6%f?j{T5@!WOZ^utOvQx4+kK>oZs)(F_n&+HfG$!9c1^>Bb4+&qiYi8<*#>q zwRcl0`71k^v}BCc1MVl6FR)3!Hwco1R;P1Qy2RmYw}z}~?6%6n{U8T%&l!GIqq?%;{!&x+?_E=V~sa3nGhc6@|MJz zp=>EjHZzSx3|I4)1i&2!dI@inIexnwP`fK!+NjU-C`-2AaPb;K#V)=~6rv_H+@y{G zqC@ZukU-Qw2_r~rqgt(|pP6Ag$NyLUs*hu?LZ;>r!YMKF0$6n~ zF&10x6xLKR0y<~%bscBJcy`yB5H`JT^A=TAqmor}*o0uX63Y`l%(j}XxYym7GyNLW zyGR6~y%<+oV7txXR~zc#>F+y;`TeO9O3aOzNUEuFxOGcF^oiCw_E z{-`oD$inF#!)H`RW}P!LGd2>;3}+taA^(c!-~R`H{~!J1fBYwZ_fO6rF+X^geDRzA z`?vr5|N6iEFaP8J{1^Z9&;Ixi|6n%5kKN~U&htFu6Ng>$8J;v|{ER1OKKpZ9kA8jS ze10Us7$#4$yLBHYe&!DVySU=lZtsao;(U@ll4KJSrLeOR~J%lbTgEFia8-KQ>>Cw#Y&yyl4K(8nr2N z%)6I%pUhl;InJ{WGS1i1&SPf86d=3~eS?%WD(-HlCO|@VpK~UGL{By|rjCpuGjrXr z)lti&*RO$ON3Xgs7TH|gi8SWKnWnR)+0bUsndvslah@kNdrd&@h678DF&zK^AOJ~3 zK~%VT3Xa1jcA~_&Dm5_baJxYZ?BFH|XWZtM1Q>^8w86ABcZVlfAf^%>7?AG#W`Im1s?=PFQIf0?!D$$7#@0CDUaAqz;y4Gg5;x?4M zQY00#OxV8R?b(>qZ8w#Z;PsRDmtZ{$u2t;|YCh)>GKhg*b1xYH;rHlgAWbJt1Y@?r zGq+4_SjAH6s#E{%rl=&=UU1pfMY>4eP`di*JmskuykxNfs;U;R1PAkc?K#seWwmb- zd$XoXFYsZOee#?++e8^2_>9j!_o0)-D!BDus_zy+uMPHDnHirnY{nPuYMy4?9%-a9 zV2s>6bev?pjuCBJ!OZMF(>il|d;uLo5~yB7x|YC(3edW`#vc48=B7&!1h>N;falpc zM`%Uxs^7Z{0(9$8XT>Mac>00B!jbUoGM(XN-TU`UcWj^#^R_3ICQaFz7BLHwkfd(~ zqjlbMtx2w5RI%sT&*v5>zVX)aZZ%dwTuRICcRW=7o`1$F3JiB9Y-f>_Z%_Je2hTn=kUNX!8 zU;FbocX_4ea=H$Jk^%#XHPRBfc_dKy->5{Qj_pr}B+1M?<>8oJ2W2-cIFf7vOsDC8LVe?9RZ^isc|I(Qc+PgC15}?@7Y^CXp!2+R-7v{M^`m<;DXxkV z;6hQqn=wgJ94hc^5X5W{oyxkb7vGFxcH@vV)?ziNrFhK@n;mCn!k11-Yrkxxx})Us zI}Ehu;0a?|VWzZPycKe1s5}PkK3a9&UC;B(d=Qme5TcfE-|;tzM%`y;1{e=!lflv7eLd&= zfW7R_>KI@D9k~UMZEwvs$0Qfh8H;BbrqZx!xZ~U~``+3I-a7EWqcYZLx8Vie3{K&> zjcWZjo2Xg>Pd3MUgHD|B>*lFjzlHSp&)v^Cj|`*lYLBx?pzF=HHJhmz@Um5@Aht!_p2vg#2YA*N5;7Y|Ht$7d~ta@geTi{Sp2OAO*aYAh*2i-y*zbybmTG_ z&YWAPktc0)ab||+d6-dlADP`9M_e+Hq;VVIijH}s9CgO2=3IB~_LufM_21O)0#Zf5 zu-`;cB5LkBFpy_6Gb--x2CIJL-leCbseJ98GFKK@+oZa31QgwvW@P$bm z<)lhEyK7gUg0$;l=0=iiZe50>bKIrH#LeKv-&@Bf9Ocevl zo|gsLmi0{sFw}raW(a=H$&)2!r+jz?n~~79tQaT(X_k5R(V^OPa67hl?oRWaI|4U@ zLn3;R%YfW?*^p>nbbnW~;X8g{J}1vt!3PY1Y{M5c5AMSRk|z3H$C>F7Lb4Y( z^wML;x$6~$qCd6IOUTeZeVtT7aHP`YcHf(fq+@7h1ZYEgt4G}~Yfx6oceq9{b_eg3 z!6qyNw;mQC+T5poIFOKxI0Z?9(`qBr|8ToxbN6A$4lb{5qa6ZJk__ zWMPsWr%i&&^6V`{9pHMORhS81bp7XP+16ccgRoIAg0Sg&)s-iSGsy0j^Woc*N|Z5A zLMsgIy2@*X3-`^Qh@@}M9?<$bD@B%82Z$D=5c&3E3c+*rF- zvdi0jx$>ff;GAq9zMbUl$7!8NP<+F_CEEEL|N6iE+u#0n@SXyloJ$etkyk#>HKuwC z0SAaUZnoK@WV*(%G`eKH;Ajb7RflPV*e& zfkxYUKe`W1#(+Bd20=xqR!0omTMaGBsXu5+@*4ssiF1$;r<+9IU^M!+C9g7BHxF2N<-xp5r=Duc4kc}LVhFtPNlJ)KjgJumy z6kZ;lKJN8FKcCHpT2P)1zoH6!)tknG_fBEyT{>H!gxBkhW{*#}}*VhI&f(b7umoMtA zk-A0PDJIb39vYN1q~p}THp#{_tz{EQlt(sOODtEakdQP4AtQ$2JB;g*X%1FsRIFIF z)}P@%%XcPyfSz|1ba9KmBor3l>Q#21vcYlYMkrb|jhk zJj783eU~r>2FJ#1^c-jO@KFwPMp4ga&KW(Q;~YOf&iQfv+8_SK^Yst^^dJ3K|Mh?Q z*Z=n4_}%#bEPwg)`Q7rwKl#z5k0TO#B3P@XqjWr-hGX7?y`*?10a6$EHU&iKXY;Fmw!9D2akA}~=H~0t z-3D7=A>z7qKm$xaa=H|ixuv5z$)nVOl|In0HBJIeYZa#|MHph+W^!m7b3YOjLe6b( zFu}S-%?sltPgkYBp6HojvlZAxNl6F^ka1mJGw6@tWDMa=Sq%(dB_w7E4Goi!3j^IT}+ z08wW1NHV2f>^or`ZX8}PlaS=0>7z2wD2<2co!wtn2Mcz80tNzcJn`X9DE8x?*PB&b zluIQqvEe#ID4uunvJSq(BJJE)z!{fyC5%i9`BI_shTY&$3GYwoutGH1kK6)a8V4rC z8y%02>>0_MDJ~-^v7_||^2h-Rwg6JYMDgB51Is`yRyy$2 z;4KU;8$?nTRZY_1Mhy?u3!JxDF}90^G!mFcu`v$*E>!C$LfaCJ_rwtwmr@0wf8+gjXyWS=ml9v*4SWh zZESd$J9844WkDy}I%7=4rDD7G)hwEA@#~ZZ)qC6w251uLuU-8BoQ@A2Z}35onlF&1 z$XdY$m(CWisWULeW-g`B5*w~%l$qFoQ(=H^n%H>G z$@6mnC)nq(!BJ9q=1xmT9!Lj5(}j%+pB$&F3{_AqGX!F@cDsf`F_<*yzZih^SEb1+ z4No0{1PhsY0YTaK_+13J_?CK&4=U<+_2t}Q&yi%48J@fTIH?wi?3vl*$T-M>zNBR4 zJOsN-@G{s$-#*1lB?7Ga6h2=|ygw5f&v_lGb^dnPW9Vo$GCUed5|ZGV&U1kopo?j4 zDH6Q_Dg=`E7Y62Y*lp)^rc65Ej{&0-Vs^|EWu`=p#JJrAGEQcp(`ShCwaiR$8woV! zXEnp@m^#g5kF${wA{h=rm+~ZoGf4U3{?I4I8>2kJeBXUBYS>9rZ_X5ejA-n$^8~x~ zriNFT=4DMw`cQ5Hmsu$23;U%tW#cuuV|Q779|}n#V+rG9m`0%L<|W z8}aBJw)}2Xi53os=mi*7^6Xc60+Dw;PqXcJRSaT$W3r&2Lq|`Tm&{iwDI(#HfE-G*}(~21zbdwg=7viZ4edqx$7#`2VjV8m1+0=%P8VL zwO6<=A`2Nua^@IJt(e5~Ip#ipzPq-7$n^7LAEzAYtqE^0^US^nLSL zq*$jR#FIP@Fv+jC#r82I=iIP!b)prgL$2S_;VuD!p!6+Pf-|#wY5kscqLn&Vn5Ljs za%^o7dvo0t+x4iMwG1)a{IKiXM=T_eu8<_p$R>dn##VxMSc&M>gNxW02#$u3%$#fn z7ed7@$Gi_WGNe(v>gnsJP)7^>ID09b#-W-Hi(2AHNrH1}bq$8ogcCI()RA)M@**oN zDa(L#pU_)_01VkaCJn!m*t9W8Y#~%bfXTo1hkw*wqKrz10qV>bzE}B5c-O3pX(F^~ zO*UB_NdK?dy7*J*oI~zwBR1RG8lopC1~zG(R2IBE_#!|Gv zLWs^j4+#!&bo7-!n_Os$!$!>}X*9{kz||}Hg~NQ3CTY`M39s#{5wg!FxxwY)9DPGo zGmR$6cPRkZ3ww61WZAqU09TB1ZN_Ns>FZ1#G341tDSMiYq){-X;}xK+48WO&oSr==<7E-hpyv| zTH$#Mif+&=VjW+&c#JyeCX!M;NeCBzEHh}dfqMePkSbefdRVvt3%$Wv#@%~HO`HY4 zk6&zy#x`R7a_Vmj14KhMNiuCsL+!r1_n$pEB}d)>bs_lLhdgODjeX-BNHE;9$~^*7 z7)Ziq8*W!D2KVD@TvLUcdV*xreE?O>t9;b3VaZd-By{yBoUZP5;ewLjE~`V5t%F?e zpbbED;TcvK7v(>6 zPWUh%&JR4xWIoQxeAfQ{nVgHq`NX5g^O19AhH;MPlZehaIpb%XPaHmznas>No{#6p z%zQo{>gIFQIcLtye9lblM9j=NXQo>yFry=L<`|D>X1pyZ>Y2|lp2Me}EliR*&iweC zGuovVEyg(gNB;(5CNoSAQHJ-=nKAThxxdYlnkhH(K&^_p!^7|SWUUCJB;#A_K~nD= z#4em90ioU6OD?e`R-F_n5%%e`NBy9@gEg(tdcYUNI~~^-fjV=&>Z+aS)S(@lfAQXL z?m71^ix3;Q`rl?-$<#+wxZ#MsM>|oG1hQ#t#NUgw8=(~|#i76LQn!L6GlvVm7p|6$ zjiLd_EdZz3QFW6*x?8wIK%$vs@3qpCF>Wev7Rh?;O@xnG75c!-nw@Yt^iE z&RjHhvaJgRX>FxsX8ER8ioaN*TByfOLr=rmtFf?o;RJ|N&&p5)^V&z$&6kVnD6DR@msdt5@jh5fxiC~(CGr;qvt zn0IM7$?pMC7o!H6(~V6ppLbKng)_6+)h+bO^$QZp^LrEhPDp`51A|GTae)Y=p=yE~ zYv>WwffU9NNc}@i$G?QxJHbpZN622Oj`l!GR67JZm$-~x(BPWMdeoGPmpNo^gnbVX z-#8O+%r-PGz6%*5wsh<~!(~Y)e5C-TZAhq&X0;my;STMPEX(mPvZI>_8bp2F4mNhhvb76*CPAB#x^h>*?e zm_3b-_y2PBcB{HA(R$YNwEoTYyHOEEC7yUD2^jZ5B!c1{hzBZoBi?`@-jk4k0Z$SF z@x%i!K>~tg?<8wfdp&rokG1hzYtA`F|9Y=#KW}?mtLoLOHyChVnO?so8^`NvmbWoF zZ1awtyJb=MN|qXXt_xBjJKEx+=)8#@YIq9^NP0;UI-g4}*lL^W)^9obbM6q;Qc{A0 zm~5jn6kg1XMS6P&LpIxG4{3vyC+XhGx~XV@gX#QRFF#t-(CU90nuX&oluF4)4yWTU6pyc1IbThD>I+ZhFt>^5aomv4^~l40L!q-XTo_qS{RT;x#` zSVUWd)tw>Q?o#2&?#!e12@Q3eExKlOn9D&k?4Bl=^S!kpG1(jMg~8>Ml2q!3hSG&h zRYpZxU1xhyKb6(&)@~|6kyPJvI_Y0F8#0h*Mwn^&`I@{o2x9YiSxcPH6%IN9^ByLPRzh3wgEb~mmtIzx8d2+JRZUiS?Me_Fw0HxExsAh}M= zpDrtssj2bwxOHaHbau~rvW0XSQUNb0%sk3F1Ss}8%|f_{2$wMX7T6@26Qm4xpUDOq zrB$K-XuG>W_jP9DB=2c#x*AX+O`sh_rTjBCsU=Cy=4KGhnj%?-)g>pO*h85$6!e}-)hN&2$Wx=@FA5~+XJ z5H`&3StM!kHh%*o)sfH&EYhopaDCg4+ViX8p5qm5^nRNSrnaFPWRbMIwy0%dI`5ap zVBnTE39r<505}R}mz#>WeYIPZLLl7&2fWO12khZmFTI(V^shunI{KqV>mL>UJU5$P zGp6ai>8NyZkG}%k@rB;WrO|7z+%x*AWoq{%`4h}NteUf<+5I}_X7pYGU~m)1@cKm% zHi75)aM}A?wr%TI`6fY9zidrNC2g9BG^1XCwz0E&)vdRvd9Ef{ZHLU3$FnDWQv_7h z4Z$=wy&t*skPdkuaNqus5RyLqSbcA1P*%tih%FKw3cvICH9gueaN0um;r^!!03M0 zr6T0D?Vfw5tluU#krg~W=Q};Vt#)oFaPN@%ez&)IN`q(n7zJ=qhe+=pb*sYrNkw(K z>_BY+2Wd9kJ(qG`z+7TXVu7i+9e2FtP$UHc3pl3t(fsars!C65*J=ZctT2@vk==?{ zJ;NJ&$qpnpAPo?`rUguxna|4_)vO%{Q^p`~dAEgCVL(DapSiG38WDy6ZH`owYZv*X z$0fmwVd}{%STi*tD8rUGL6$DMQ=c1#bHWJRC#D* zDs@M8?_#hbLTefzG!LQl_0e^Ba>nV_nhyr3zeK$Fv2h8QtTyN6!R;nsCur%*4j?y_ zz(NR^<$K-wZ42OBFW~iKp-|=A@kvp@ZAZM}N;hIAr1hTUN*?c=!^ZJRyA=-c@)FDh zBA;&(qXS6aYVTXr0k*f1Aq~VV&fHFejYqgN5b!FQl2X`Y=6*5WQ`;EvTB|Cvqw&*S zgWRPToDP(&I{!-D6bmbb^)J-9w&`>_N2)jD&M`pLxo` z`T;lJX;?7B9y1~AS(fnZm?e!J*}N}1nZqz~_o$N65f?QAdDz_{-lwZy=QT~ZrtYa# zo4?~QGhgSvu)urvncTPCJ=aJ3CIldnJf5%R@&>K%$}0LSTf}->Y`l~{uW&SrS^uaD zCUo9g?Zp{M%x8yzf%uUZ-2$T29KcK(P&<4wci%j{Bq2_m=NV_t_}A`#{`daw-};aK zo&V=wofqV}3k)jy@m0z9NiYJCYBSwa7GK2Pa=Uqi5A6K9e|O_6+WRiKpoPJjn|tO}?Zw zDaa!`VHwB_ed-`RHFPoOpKEL#cwR#$q2n+TDbO7$>&yTEAOJ~3K~!+VY@E-)TRUX4 zLIa_MJ&jbt2J~t4ubDh!xcMz%$$dZXkk(8eJezGQ)2gW48XzNc|lid325Qxl}Gc!v~M0O_|sBgjq zh`VQUcaJ3Rb6a>`j+nFR;1bbVl#+YW2alY~OIc>qr8&-B*&Y+)l9)QU?n;dHSqBvp z_-feKc+Ql#I;sa+4&l9%C&I#vS(vwe3&N<+``l69B7FiaqUMcLR68Gp+szjnpaIHj z1Bo-uk`NM{tBNGyjV3N*#CggoyNA400%fnbJGP;YjM^VbB;4^1_YfLhASFbp-wBa; zhWVCSxW!7JGVdDd_Wo=(dXcYR1&7eRBC6Y||Hw^^W^cr6`xNsa<%0wq`UKoK6AsBFx$1c5n!KDWlYF6fU;&T| zZ+8>vThor}jyE%;_EfBtEU=9!wV; zz-+G8f-t=^lzk^ZA`X+;cXgY{7PLe<0)wAIx+dS4Z--c^=XT+&p3=Qccb)a#9m&E^rwy; zK_Mw_>nVY2K+tSji+2_viwcI2aF%k+%eQ}y4q{nJIvlUiRk-&%ubsF)=?@Ddl1INt zCnwx;i_b#G3)5O`0^QYH`gqxU+Y6Ezls#c5BjTd?+(K#0L1LrIR={xpzd%60(V7$H z*tJ5$nH)yf4${A6mzC-C+nPD1SFKIV3+U6wa{DpMV-51uh^oa%dw*&DDbE&B!3`A#B(x3(RAEH({XyU&Hht2eR8CRPAX{1Nq^IUjk$!4sgy;%fKC- zSIImv6c0R^Twr7wQu81t(d@}H)nWL4Tfm+OK|;TUq!CJeJ{O`rZ3DYuoQ{A-4t>XY zc4lS^`zZ$Dee=K&43}4VZgDxgK z6GF($2wBc+Z&Dm{CV>wVFp% zNN!GRj*;z{D{R(EJdLfv1;QEK1$O4v1F#UY=D{&C+~A@~Al`zuhers);9+4K~d1B%4C0lNs$9p&?3?>|*0lk)V)1z8*=aaKa z&Zf?Tp(Vh556rZQ$-J0Ig0L%O))>^sBuNwqUuyg$8&{bfTHeX>WW%I0Uo*0Y=`)nb zGJdb1Bmvubvfd5@*QiQzNC$CIE+A0p#xfNuHBEr!MgW)^XCRVzVZbnCFIi>tXgu?o zw0b9mBrV&~;a3IO@kUpj9rpQQDrB(n6(UN?YG%Mu`=V1+N zlA@i5WSLvkCS;gU<;wQ2-cFBZmtd!sgVz1l|H&$(;`O+7Oq0gLy+1_(rrS66Vs2}# ziZ7?zPEveaqyX1(_V1a^(lZlPCAsEnyM?2~%!p`a%za_-P_;F<;q3CFbr{KR47a?f z5Kg!lz1Bf+7{N%`#22F6r=kV3B+g8tPTI;{XNtVz&kfN6mb$6xkaT)+u>;q}Taq|; zlQeq5NPnTH)u2G_+WgrO~M<(Jx-e!Bs(fntGo$N?m|RhV23%*o~z zYsOBq>g9+eG^IfGCP~4#O=7kXxwhCg%e0qksHr@lrVUATS7jD=s)z3bVai2_Ap*QX zA)8cPM`fwlz6jXspxtk5RGljG{XgWYNeEku6WR@8fK4&)RfE8U%OgOFbQd^_S1@KhJMJKfnGozx_17{^-x2Uw(Ri|9O7@<@4K5 z^A|rozx_17{p{~QeSZ7p`R%9q{g>zWU*`9ppWlCZe*fk9?dRv`XMX$T$FDzqe*f|L z{pZgwKmGXir~B(KKYf1v<;SnT{P^wHpML-K^ZPHKUw{7m`pf6{pFh9;{P~ODKmXRh z@#FX3K1KEJ9o**v32>etuQF~(9S^*2$S$O3hs;b;9$|N{2X0!bnG2;-&FH+3ys%z8 z(4#gk1kujW&J4k4!r=Uo{Wt%;fBSF$<=^>#{}o@mUwh(Xi%WgxN6XZ8PVz7R^&kG= zzx^NnyI=p!zxab?1aW;4!@%;$NYc|O4N{G>L}<)a^;A3mS?{FwRpIL`+@`ZynaW`5{{GxNzj z%*^w{dFJ`(N1pMc&p6NX$PCXg1m_u_=b8CD5Uv4NnVFeqW|W!dnMnwvkDt%y`9Zzo z=epehGiLM|-uNBWai;E3S9nHMXKavuQ%IT3XRtezJ#gVMZ3oa9_H1#5=E4Rr%{#!{ zxheo@`OquIiKvtT=C+`ha6@TEfw!!7-mLV^uKiw7mY3lQpl08^*f`pN0ddAu?#`Vk_k7RgrOWe4$Q!EO$9$W&lFT!d zlr`t8CP>z)-tmzv(l1adkwRvkTDS*+!a_y%w7$KDf>0nORSHm=Z>YdY-8awd9&|KG za<(8f+UvOhAcz+)^SzobAeDD2RCV~iBIzBCCKN-h*tnS1ch=4nEY%bel~ow95{D2f zS?f~ng>~rezE%%JZ+Rd@7(pa4wbX6rHS9ZDV|#i9i`o?0mY#WbmpkE7(I>nKu0eAH zAsAd#{r(533V=F@xsQ)j*5=KkeVkG=dYTU2{A=QjGvv;k0gX##^uE;+RHqH* z`A9DEXAFioGhHO!TDJbBR^;qK3ZZ!(eN=U8{}Zef-);e*MtP?>K^_RkMIeI({bE zB-s^bo;|?&zI&$^II~2bv~j>-`mP~RvU|k3*g6#G7%m`?=hMJMBkkd`_ciR)o=L9V zk}S?l8ZHjypG5q2xmXph-D;NHVw+kO2ls5HcO2>>iCZuCS2?v}LL&$bpU+Tcy7LKa z`@6QaP?0Jlh1vb#1`@u0!6AX&J=tc?7B{aM+5<8jAX6%IY+=ywN*Alq^ha8Kh4AuBHL{9tra8Iv{^sBI zCX|AcZD#8)7elXP^;z8m=r#)gLPA>BW6cO=yLbbg<}X#ttJk*m%iH25VWeHX%>yL) z+T$d%jOf|+*{cUlhIr0b6${@C7GbkXxVT8Rusux$;^k49rGlV#Q-R+ zgic&sVXs~@kGP3FyU47x@$9nq0wEh{n+@R`1!sG|^7iPk;qi_uX(AB8nfpT(4&G@x zxwNJkovMXjEN~3&hDnjze*XRbIw9??P$NTfpD|M z_whFnLar(1oMk||Sw+iB@N@4sq|jLsAoUgUUtndvZ7@%4U^=~(({Ufvt_ zqj_h0fN=1v*Tvb~`tADve*DG~=|C!>)|e^Xr~}n~Nt89Am@Nif%WpjhSzB3waJlgP z=b7HGS?)OKKH~p8z3C~=IlD;*tdApXRy^I@PPp+>7nwC%Hw~95mAegU5<#ERa3dnI zSI<0GR6S?0x3yGFJ#SX{431CpPxE4zzP=5Kut zVKlA76S;Yz5}kK`(qW##LQ<;bn==XzS`poDt56+U`$RF@yKAG*Oo-r&P=FW2(!`CU zlMWBkKLjGzJ~jAhsh7%Aw}+O9ms&_p>11q>t%>7}>N;r8Gntujo_Smd7Uw|EbW&+Y zwjv>g37tdh!v`cL!X{f-)HDFEk(J~YFoEz~Le{yXpl$)n6!5*cUPAKbhc>zUN-~|d zgzRkx@D6{X;wwe&JWrB(?ag$Q2l}}O1qnJ@g02^8S*?9uN#B2fP!v!H^?xb{B!kHp z?%1%ayTHI!ch&kG(;I@h-m&vt{wYtpu;Hx=-x|{1b3^}YCrP#u2|>`@8QgX|ueKz2 za-ai&HV-D7m_ohZD$U4{`eUZQ3TcJBtwScknbG9HZeq<`ku@nF?%1f^kg5yNvZ{a* z=mb=)z+6L6Nen%1EtDjCcCGw$Ka6X~t+sKO=jjlBAR*Yv6lW%+sjvdIZ|VKz(T8iO znQ<_sSlF}AzN-g97V7mD4^vI|(62`a|1BmnXP2~sqiFB^oUaI(-6*#*{bT z5IIHupFIV8Lc{i2;`YRC|AM1w#T(ZaV)w88^o)LDf;My{VhhU=O>`y!PM?VLQ72zJ zx+z&dyN8~CTFjw+o}Lw*F8?X!2Y17ho{XG+97+cBA|uRUq53W2T+x?jYpJWp@k z)y<8&F&mxjdcDdY_6j>iOQSVGicgapNeOXpd7{~hg<%Jjz50POCfQ`3XLsk-XNNRg zI$Nz9i|LTee7$RB3CQ$yJ4%R0X2}d)&%P=C8jq#I?w9qdQ9iNVbAgerf4nC_60j@p zrpCSfUr~&<{rMXj?zYEb;kiFR&Yg5_e_-`Z7INcmvROqZIld2|6qsxv8!XOOZFyUt zhNNzv$>x@Pl9`U%2}RH7-2o{#+H!I>LHfKJ30a&3qVX#$a*OOHuBH?8?iHtG(~_W1 zJ?&Y#+X)M+%sl`5Kl;c2^q>5*zy9mL-d$zS1|DksAw(!-gO~aUJPhn%6NV>VhEe=> zL7-OEA?jAWJ2P`vp`|{FIh}eg zAmDAycp;OvoXZ4B7{qWM%s!y+2JotyIN)Pu`oyu}D;d*WKa$mtuJa>c=Q8BTSMneK zoxeT5_{YEaI#{K^&4r)u*>%Vv zNWN%kqB$&o;NU}Wp4}tQNb;4Xj~0!j$vWk~Z1OyxWWq2%Wa8eJ4%d=%&?0)&I zS34Ii*q&P7lFB(yKXzO?txrxe<70pg@!n}T8^=x}7zvYu)3hSlZMg!hV{cvQ)5q_~ zK3r3v1Id(Mve=z_ivQ9Q=0K;m0FpRU?-$`J zb(~htX>mtX?#7!<_jME2kj!n+W`A`dX&ITusg0Q|7!l|IM_xD55RKNB1bdtay;9&A z$ijqLiEdnY6-v}T$CyNCb8MJ6eMqtW%_ll_T{w=-D&PDgL}prc;{3>lp4o6n#)%>M zrYA>Yg9mq8d^2_bc+K^aK~%nWoVwz}-Qt6Sk1m1Batz#P7<)liYuZY9HpRZlPJiA- zPBP9%c_TbI5LUxZ$XY-gx%$d%4Un+AGn34oI=rso?AJUd%#6Exsz19h<(y~W>=|_a z*8X@+&avW|~; z;3xi(KW3g6-R>^pOyhThW~Q6Ta1E}>j?Z$~`Ui+J+2h!83jv4bg{fJBwwsLNZ8FY&z>{4YBDAXCv#WBBO~$zV#qE;O|tU2LlN-7uiy>J z8L`6+yC)Nq(jfpdE}Odm934D1z~jtkx8P}9j%4@ib~R%JVoP&`2r2PqcOA!}=avw4)~37Kc^G>&eXr#4`ShQpWwnTL96w`sj~ z%s87hp*L|u7#(RUfrM@mnAkEP33;Y-wRc=}QJIGIZD0M|+}HTJ@KnnRZsuXNU$p8G zYSO&q5$GwknuKd?W|BQ>3ez;|N(#x1-#^Ar!g9q+rAg4Qb99>FH#{{dWcz}IydBP- z<8;j(AxQ<79C4FrMom+&r=U1EbQ~5uo9Sq1Gp$=_438bVFSN!}%-p+Bx_or!Zt*yp zQ~U#?kQ`CpeM$g6^L$S*ZlMuYFaAZ#>zC7RtX1M&U`if_ZS#EXznWGrUPE)KW)I66 zXP$HIpfj`F>gY29Cqa-34?NFa=vw|1Orm3jNRiVjh#!yS1VVaV>-iaybI3fy*(6Vh z)fK=oNpg?0q8O+l#3dN91-jyICaNrvX&TL{>2V)34Qb*qz1V|>shgFh1&+ON+R5G%;vYOUiU)fR9 z9jYMN{R50cQU)?O?ACjPOpeZB^$u9(B(-}I5eQeEpe+{%H`%akEDpg|@qFvw8x{UJ@a>H&gcvToQ4U?H%Ie3~Zw-;3iiVAm#bL zLAMYfQvn)|47TIc#jGrutz%10i<8ondh>7K2I;1yYE-vCY;j3MSzz13w5o==nJB@@ zgH(DsEtpol2))ZR1(F@P?g?6}Op+y|rZQwMB>+U+ zM47;KI^hPbrrG`~?0J2W&uUb{0F^Ssa)|NWG zRc50nATKYTWUldYv*x$HI`uIkZ68SP4_q3`jE(7;xk8bIXXc#4I5WvHPxZfzOhv7w zZ#!qv43o6|+3Y-%j&&^gAvpzcQokppk(i)+Z~azI8L!=v@U6%a3EoNSPHxgq)x2DV zB*lI<*vVVwjzVK&sh$u~T~;P7P_JAAZ1OO#JO|VX;aVE-C6_k-q~OPvL_KF7lO_W~ zPVzh?lt(VOh&r;ZSMGq$ZLw0k87un@FIDLK4hnH#bt>I$xiW1PZW*N)IoEoHLjT!I zjw@4vz11ysk{{zoZ7B(ElePAK`Xcb=!KO403ZNKL_t*NOJ&q|_B{EU21`L+hYHXt zlk}$V+kTQwb=MxaHt9Jk17x!Rvb!|)$;^n^-P|W&dpGi4o2Z)|u2~5+i{y0F{i+5f z?@$e;rbsH@1REB4*Xbv#`Q^vwpZ=47_CNnm|I4pG{rdU*c*Bj*)Qxj%I+_nR&QzUY zE({HDW_GZ$M*wh{ zt3UQnplkzdGi-WxW1T-CZS0tVMF*BV*NhO#|H|eS6>bcaWcqc|A6ia8@7Y{8s@tjC zg%EkM?b$QaNze<8^!D?v^W=_B@7X~%jyJWsFpzoN6UZou+*DFijHUxBV2L!Xw!hqy zC5w|?-{$Xmx2e?KuP53Sgc+}t4iVRYQ~;`d0;*!!zl5h_+sfBc{@XYYC zT$0^0y(<@#JJH@a8MA8?Nqn?%H&2xXvWYxRsn^Z{h&5WO_})=X6B9+z%tF%F*UTLb zEi>#cM!Hl!ua*6_Gi099Td070@$}3b?sWAK1YpdF$ji zF;ukM?nv4}3g3HjL5N6nLjC8T*b6>Y%zL7=ox1Y$$-E6isXU~V(sIgiH)O{NWG|6& zo4+z7OR(6OX};cCoJFB4GG|_zedb953tqiC@V+;FJJ}VTT%a?WcTktc7(_@hShvpR zr0Z-(!ubl91uFQx==R%#o7-#vYis&ny{VQ&q|Or5b5CqtXOe3)tU;3hkEyp=)?{0< ztH$)$d2XpBLwrYm3S89GZJkV1kntcrc!IU=vWF{7A=3d1N z?%tFiq0H&w?(XwEr20m#6*|v*7%;u3;toc`Gqd}d3(_)^gk?xwwRnb`Vf9F=2|8tA z>1wmtHZ$uYOn#r1PLghzKwm?Xf^1EB&VGIS+TC;YM1Ghx2!WSutmro2+C_*?yE;5@ zD}(GF<|z@vfUZmlQ16b{z%D#tnNe8?8kjDYa&_unyfp4`O+&2>Surqm&yf#B;PQjK z^=`peMyLD5vCZVwte)^5nJ&B+9Qx0;)i!CmVF-B%a1-npndiRVf_Z>}Bm;JEMC)c% z@2Of&$F4shn9!Kse*4QB%$N-I8cA}IDNhQ2sSJ<;m*ng&*NAnTyoqIx4)9~@+k69| z(^Q!b!!-N7%b0StIlP1UVM=eSr8cQwCzN!dn#}IC*O7ROn-}+_?_3}d@;uM!)S;BCFR~C8gvp#L$x7>#`hU7IJg_STf?DEc{BSWS(ErS$bndNs3jY1dNV4~^4p<#RverE|A4t;s_oklcMUnUP#9L^1&Y4uI8s|G6cE7XuyqVRJ z0Xgnnl_1Hz4pKY=WZGRlBa_{I$h3L{dz=QI8DZvKs-g~-idKGP7ZD@&pqUbFpy;Sh za7c(I^CU>-&N9iaPp|uQ+X_gNojanlJCjD&aQhY6bd~*~S2t8^*Lk;BC{6K9XVz~s z6OQ`2QAkQzvXJGT^yWU4YuWuClxH%fMgXkWgm7m7jikd51;5a<(Ad!$s=c;z^v>FQ z3Zlf8LsUU?4@(m@L%Y7fD}}l3C66@CScpp#e?Z}E<^fj+lh~D$^U}K{z)gVAOdWEk zMv}{j8tIxUwZe6WJ{Mc^E+1S(%KqDm8VMox(;w3c3$&oP)4O$toi(x^8r2klM#82f~_6ah-Yg zoUgCv?4sUS39?(uFPfJ|f%k41G}OvUQdD+Voq#unPJs;WRqK$;48nS(b>E~=T39?b z0xOx{33~QC>5MP*{W@o-He~=izTE2zfg;@_?(nK5nxyq_eizIWhx#8VuJ!k%){M5% zJTuCNo%{ZaM8!$ND&%YJJBC_;CR6onUe&X(8r! zova}{Q;?2Bn{Sj#UW*nI>M8H_*|yoK0kdt(~08j$#`o>~wuS}i&*wlSDbc93A zn$Q6zd(O;r51=mQ^UU{3)omBU7m$9IH>TXb?B0D<0m)Q`Nga9;z1#4Cx`-Hfy7PsG z#MSnYmjVf`>8;6QKjLk-e z53-)geBIw@o{4=|b#UR5%(WYm>#3Ih+KcRWY?4`uKcy35w_&RU4T)(2m%yZR#Y5Y?| zo^*|=h=MLmc&H&_T(w3?3X5iSezfz1jkQKCCOzl5JV7h3#Ulevv@R zs@etNZb?bdNZyzo@SG-$DD*|0VOi;tw|ml1%5A%YK|EGOy3kTQ(Sina>7s!)J(p04 z^iakoLnIwNY7lvoT2Mcop>)sSU3PRgPc|38AH3p+_}1%IAe8vmj!6_flVOH?rD{71 zB?)ZzufQ0S#o3URFht}y6E)m133^me46#lDK)h?UNu^Af(;qLX0o={Z?ZZVEq)()2 zKajgkdUJ&MpyafL#gr^p8KRx{e3ZZj142%*)#vd)|DXTY*Vngy{jdM)##JCi4<;NA zA-xsu&PxB;kwU3fDCa%<%1%0>)SRhSM<)59WYeM2FRdvTSoUi_d;a=A{U877-~GG) zk*G4+51BbEjaKl{#MaS(8<+BMHU)2`lVX?jBl5h16c2oPI4~>Gs)Je$>a!b0?d1_- zgW|;{+{R3jVet?D;;;VYzxtQoKT@Lr5sJ@*Wdf$*NyD=9-~IRh?f?A0{Kr52@gMzg zKHvBI=XgHnc-(W=-Ou@aKYKo(bAJ2v*X;iG{rmp*{ny`q_5AkRZ{I)PzkmP!`F#I= zzJEX8Ki@xR&u{zPJ$uf^^%!VX5}Fq87n6RG-;zf{G|%1PuOvg3bj1WR-SG;5hhPFD z6VHv~d?lHSJ(59x|KLyn7KU|TTc>w6ypbjiT*g(g`wi<21$ z%`1XwgAC!Ot3Tpk8f@!fSx<)Eoa6~ZK~(0;%RI8`P2kp+2KA_VM)wncA%Y@ ziklhPC?E1g3Ti^)75eLWZK+efe$c4L?x!=GTijpXcSs~}Mq2HlA_T3XB?YYAVxl4Gu6vw8Ed568we|3LfSpZ0SaN7fHMJh$z^n!YSc`fS5^^S>* z+JgaEQfQvz=i-d^qL(<8>#XO#OD_xb?wSGbEyiU9fjqQJGt1DL@MYk7IHt#dIm?9nth+B5H53!-?v<%=vWxkNG?AbA?8sWX-X z`(7!4Zj*b+SlS37oxsrB6x&zT1RkG|&GN8>%kf&ahu)QxBxl9m1G?nCyA_;G81|Cv zQ#Ju9!3H*{&-SyTo3!4Mb_g06;yJ+1gJt8LAD1i*2-p8_5;e0ZOv&fDMe^2;4zsln z^-V~>zXKo?sUI%0?}gmCBN%rx5AG`fA~EdVy?%z!U*vwJ4pYTt!1GPxEyYZk^-I+9G- zb!YFJngOmx1sFrk5b)8ZlYs2Zv-)zg8fUQ_S&C=pbD8YGF`1-{uzAU)OCY1`jhi{Z zJm1Qty?6i7grm>whDA-15Xq9Sv&(GY>gue>jxjUtii6JLFvZxw;+~VK9)`?mssn^E zs1u7z#7uG^)t}ICu$k+zGnhNh;GN|O56BL)t9b%D(*qu1aL2~Aps;@G{8DjFJrcee z2MIAzA{O?`A~#7umz%l8V#3&K`AC}~bj&7X3m>TxY(t@I&FRL4%YRl1IGu3UKM|JTf!)PDLYyCc`a$0$ee1!mJ_N&hs*0+VB0`w8RM4 z1;egGMA8)CF*Js_pLY>SGB9ugSu;r%x)}8|Z5%)=MBb%6>BJUdtSaWZ54dYJsqYI< zqegRtd#)v$LMyev^}zSXBt6v|JV|b^n}J;VV_$XypL3rMNHtMQDo77>GmqG+@)MG)>yi*W+sP!ECM?O2joL|l z&GfMZ8)mtFM{)yfgfHqlkQ=B=oo02}rD-_yp_%Mrd~O;-fU8QZvnFiZD+P>V726-U zHRpkt8#+~H_TEDo?E+~}3DA7BpaX<1WXpzRiS$L%%SipJN7uYM&#qlpvq2a_j8*p@ z9?0p#v~bN=ENmt29ZeE;_hF!EAk-Rdrcda(U!#z9>#EZKOxX4HUwG@3N9*PpCu~_y zu1@S;#9@)f->BQ&TEI?7Ez4oSN#cm`ux?6Pm}WFwIQAA={Lqyqa-~d#7n9^#vZI0_ z&bc6fCFkxFP88b?t_ovA8|k_psKah18;zCY$3rrVI<8^nOJza3IWjE64E+0rbP3AT1?OAJV z&wjcO9#4|V0}|Xoq3XTX-o|_x_)eg6y6s!IV{YdFInqe75MBjpp*Ob~g?VO{1DaVJkJbr5 z4D+%+Zhtm)$UyOdZP1a3mv8;KDhb3A*Ku8*XZG}=k^_Vl8av`5+y#~ekhX@p*>tO7@G#h;FX|L1x9{^BUEO?&pdPk1=umJfS!A3sXc` z;66zR`b6hU7 z&^#F3o-|DAlS(p^J)dPVa$lS@B)EphQSSEnj-dEFv&}0fmD$OurjP{Yd8{_470-Zh z!-Vd)WViAQ712i1Xj6;i!i*~IXvrmxH@&mHq6`a!2l*QJK1w9=x%w#}v+>b2w;n%I zL5T?(L=vsLwjgFLFQX<|Z+TXGK)E9U)5Q25xpvh{x;fx;lReEur2?6cN#1_J?32XV z#L!oNhV_ZCD}gu&lOsspZFUCD7j!_BDtDlAM>)LTqs*nnB=@}tz1#s9f^WSsF?UT0 z(n0U$GO^X1NVreztc7X@b!N^w!&&DNyHpt0JjCWnL3F!@ac7V~8Z9QRBh!PO+s%_X zdnV~1`n7l`$RsQ#Go#ofax$)MaKIEgsR`-p!=-cP<_8y?j7t15OnOw!InDJt9h1Di zf6eHMrUs4kJbMmFGP=2Q$n?mbzV9xc-OPw?y{7GSXjdC*<&Z8QCC@pZ=Lx|rM)m+N zgON=VJG+ZyC_4wsD^rnZ*K=&Tjls8?ygSEWsgJRNw zig#Soe&?e4!V6p>uBl3JH@#0NDeE)1Cp;wEB)BP76s16O?!V zDZtEYM_5lUrY269g_%|ywJWyg%0|L#+CuS8|0Zo%V7Ahe`=F%No>VfY_rD_A8F^Zq zz502-n`TFW-7^_?$mh0X=fx8DcRy`M>$UH?Xc%%N-+%jr$EA;v?7@kVYM_!t_xBfw z;0tenWHFO2Q3sf0FRHvO2#{yEd?v}(>J8a0l?y@*s84cCW`6$5&w>aKY^Hbb1uCTM zy~HwG6fUPRTt{BhE7O$)odBvVl(EmaJM)n1eF=DyZ)f+bSAP{VGrP{0Lpnc@#G#X& z+6s{N&?J}DV8qYZ%CepNg)6Fzxo&d(|`Q;=kRR^%)GcPz8JYEg4-mO z^C9v%cs%Evb2#U7_Ve5K{r>&eUw`@SmtTMR<)I-Pv)>PUh_Asoq}w z7bT<1gk)DW0jT4be!|S-y)5~13f+KCj-PVBa0e|v-DT;03Tv}tFOWa^g1p?=UP z)^-48;@N#>dH|t#c2U)}bego9?BG}-K{GM-<~i4dbn{<5>GU`fZTs!=J%A*zdooYR z?okpdz-mZwfPjRqH)n!~B)AtPNn+SN$%Bh@ad{GlT_>!)6*Ay!4g(hLibsMSSo+qq z-esL9yFC)1LqeF0IlE7@Ml+Mf*+ei^zy7RX6L==r*<3sl&*_tsmoW1t%Kfi2v;0)?u_(i%9)!qU4j$XmATH6*Dq-Ng}~G+8wc&MTUN{jg}CD_u)Jmp!F7`3R$XPGlR%`Pw!O=H>UTJKxm$Oy?h%tT-R&=u(C(^Z zWc_3kOMv9+30Gm=sx-i8s$?j?OCnp&J&43bw^~b_Xif?JJUV}|S z-2}Z;e^ErOTOId#V;~s{XR>acB65i|gR=G~`b$l2T>^rTOnmc0?cY!xJ7>Or#EZGs z7$SMbvqH-XtizNK5R%l3(MMEkLf{YSJ!J|4M#$kiliH-Yt!f5T0E#n-U;VnXgn2!NQr> z3UKb-Z0(`_zCm+c_xYM34PWQxCJ-<)(VKTIhG%-m!aTBN-+g(6Aro!5kF=h*LFLVI z4IcJ61wZb(d7dG=(d&s_Us4CU@4TKj(+j;!x(g(yF}g6$@a8RVzv%Vbeklp5>^y_n z<$1FAo~$xCnW{lal?1ky?d}0CKwO)W+yw#Qb@~a)mH=u0K{8+(UEG~XE0@%oAQ$uM zTdmOvs)06V2eDM&Lw@&f_{5L_u6AHSkRJgcO%9@Z_g~;8x+MC0d19ChyJn!{frObM zgPVzK1v!a)@cHbSc_y4aZ9=h^e#?-R}kKx z%v`9cMcRT}d)B&!ONsL?kN5Lh7qoO;m83ajN(X9dCvV;*Vp`XPY3}OF80?@C#Ft4O zbUMx#9H@K&7qOmi*Td7tn_f1Gfo?J#Mt~GJ-OtQ)7GXMU>;pKTiYi3zD<{@NzQDxPYDM!F^8GN}uuM>< zyY9Z8Sq<;}qBtFQZi;w%4`dQ+TuG4QZ^KuL+>4*M?tQ~fCc&CyJ$up&Aeej7+`_zc zP2lr6jn}Na8x!D(b_wwOZm*s}H(l|=*l)n>5v5MK8m>NQjwd_`KcIgH`+#}^W-)Z) zw+k{!cP4c~z7xc`^_^s3_jD7OK*;Wr%y165Pu{rjv}xTzOY4=cH{iP<6Y#l9wV8Z< zKA)NA7I_H@A-a1NNiICX1tfvV*k$l0iJG^X56#aBbLj~)>!$IZ<& zsMtWsR(DjA(f!m|2C;+J)rSYauyj zV?wKhdIdvY+V!>Pgv|6}Ex@{yY%lI(lv`gT%1#nS%c}#w*V@I0jI7NYEfU#1eRjy5 z+TAxDOZ!B(JC`ET>#9*JAdO z+mKWeCACLk4EHcm6?&;@98hv?jp3!@ePb)5cgy!?!VLP}y3F98*;PVU% zi;qeAsOL@DK7c$k+0U8WaDGL93{&SZlnF5#pS}UN;*unN0p1|ac7t6}RZRWIWN5zf zgrwos4>MCHmp=yP>5cNGGL@$Qszck6XFw_whbB+x%qzYVyQg&_&VWaT>5Foa!(^(V zlfY?p@e*7yWXAFYxb|Wnlbp|aYEklB$AiSrsahqC(_Smry2)ECUXNOxAMKA2EFZ0k z^|WHdx-t_9GSq>J;I?0&*=P0BQ)wt|M>R0HO@NTG?h^80zY<{VgWNYs~@-oiK!^1kt9lH3=}hIN^*Yncg+qn%#J`(UyA!2~5& zTGBo-)#5OH?nWvbl<&avd^I`lu%CIv%p^3cs9I!j?;X{?#z=p%b)0Om!AeSTChUsY zs>{~6EhZr+2P#1HknX+Rbr{Vq2`g~uRVAr8&BBA9hQPP?y2?$bSjxSAo7BdjATi; z!qCCV^Y9Mfhm|oDZTL#QB7_Me&)jx0cx4r+U0K5qT><3IevAOHCG|M15@{^Q^O{XhKUKmOxC{_!9G=}&+8`7b~J_RDw9 zcMdn;%9DAz1vGpyy>B5mZcd~dL10bX%{())Z2El)fXo*hB1D#ascN1S$Vog(P6(Nq zi~gXI-DNY+AWgo5JmF|2IfHS`LzRa$naOm`XePTSU$f`)_4Qs3g1lJk!LXbl>b`wtAac?~+Q5G_?DF_JaVM&&;?=^Rx{? zoaIh>lISAbe680c;K#Lp4o;@UKq^GKOx%TF!%9zU0H(~@uI>lTx{P6{L2GmSH?UQ)T9WZkWHE9XNxF zx@!Rpnx}wVNU3)qaEfJG*3Rtd#g8eHX8gBhew=H0rH^wmAI2D>kZR0)>X6ERSuZe3i)m-G`O+u6@ z&ve2u{GR#XihD6ho@66N8~#F)4s#`tiO%G#jg_Q2DxPG#>5c^|VC$(?6W4qbqK9NR zff+p2lYyi@l~kTXlsasRTpO~kND)lJdPj|Sf&m!h;&yGub*LJhfV(qU-S~*GG0C_g z6|)ZJ4eZHOA6#^ud7_1*ODZ#VRrKlVPok5M>|VLJ_QFXyjiK*50P?h&~fY#=c988*B$$GEa z!oi17cDs3|&-Q3m)4a?55HzHh{@h8JaT733+EEO{d3i;3kTPa&L5jS*HxL?;tJ=L6 z?Vi7%$A4GYJ#o@3WhT|L@{OirFf-_p8C>U?@4G{urs&NW+U{K~j$7(I9S@ksqZ_Ik zp|Y675rrhPXPJ1@n7M|8nrOSoF{_^*)>YH`)zeMVzvDBzBhCbw7@JUJcXxV=FYadM z>`AZbhCBU^r_yiDRqiu0ZaFZ!e%)$GXn;er*2qPu3dXuf*f#QVp^wHewCUv@i zB%Pxia6iu1?1u1+%R-+Q0!eQzNZMqJu@lm`fAfkp6D1vX2)Li#wbOgf)=Xx?IZK=w zf@%zzi53&GUfr-aKr+HWHjL}d5tFRIx-+pc8GhGofg3d{!_}?b{b7a4vM!Igpa@BV z(oCQp&F+Rc?D?!deJRy_^U-|`@qpsJ$i#66s`6wS5NpFs=q8Vwbmx?p;f`$rn>3F# z7?S5vo2jmc`{lHNt_gKAS3#2UF^Nitl`=?(6ZVjN-H8UU@xRAqOHW`U5#E$TdQ44s z-5>$mJ(}cIkK4g+>1tIp(~$trcI3NfdVjXNCgVBZl20icI;9Xbu+5HYu+9_%=a_Ct zw0g|$NsA_`V>dJtCX;oCnSg1$-3@bQAq`}y&8 zbTWfD8+P|8@;dYAxr*P3(WhlXK@I5x)2hR_`bsYTgJ+#49!WmuOujPe$-SL$ntrZ^ z=ozN|HZw{>O*Vw(utPjuYzdR@`$mpffTzO-C`lBsu_Qb4u$n9QUCL-DB+F2%Zd&5Yem zP@mJJ9n!cT9w0SXx4v#BIcJ~e`I>uAa1a!ho7UM`119g5aRS>%UFcR&GMVJqa$ney z-E*F2nCx1>sWovmjpJ1+98$~A)sr2*Jm2R&X?19TI6jkeKAC4%J()3kYsfz3DzRxk zN0MpkdEIN`+9g(6zo^2@m9>Ok?@%WxnoV)pULVosWY&#K-@C;s=cD-f2$+PTRDqhp>J$FaU#o64G%ceE?>!wgWSqJ#^UK`kh1^(xgxM zX>gZjiRW}G!h>NJ*=#l$x@9dQOAg*r$8f)HX0T|pE3O-Kgy@i?QxZsdvfIpql(!a= zo;B@ahVqie7XpXe$7&E3bVJN&o~M}zv|UI>oCaus%pPGg$QQUe0ckc)fG@myk+9!y zRJy2J1D`!pRYL*sf46CG5-FJs+-w=XYXf zc5iXh@pLpvvY$ylXET#=%Dojg$ywoD4@_bOszbY15hP=_pZCFK32tU=y%99xy%yWLGtZAqH$ltO z{J^0$0KnX&=k=G2siDKgpBG-q;C0VQ7TWqN7oA%iZ z$G`jf`r;gj18iT7yLm_#R&p;RTq1OS1#%8fouvS8!W82!FU7L6eyDcjiW^av7#z%Med8Q0}GVF4OFTf$c{`|`?KmYQFfB6$Pj|sRs6WMfx zw?Cd4^xIGKfB)zI=YRiS|F^&T>93x!`^m~pdhj^I!!zTPnMt1c_IxGt?c3M4Z{NQC z^wS@{JwN^Q?c2AX{_t0S_0vy3{na1-@K=BM>979qho64>`u6SH*Vp{?)3Hp0a}1ic zoTxx4Wb3_&%fo}qbv)Z_2UY~qGu8}Ux4wr9k-Qp2jWL7ms{>5BJl7cm??c-}(q=mL zO-GmtWhkfM2XI|p4_XYZa_pYx`ReOzFdV3$Ao2r;Np&OOO#0-Q4r*>zMqhwRyXd&e z>xnJ|3+D}?Nx04*gh|Jrrl~U_Biqql9gDlhIMXZTHp*x>rPVsRZkS96xqBvR$2Wg~ zka8tWFNfHHkpKy8=eD27>I@g`JEI>Npi0Du;A9Fw$c;O-Wyj!0bZ2JVZFY=Ya=1TM z?YM*uao0)PAK0>`Jh^G4)-tJgSh{V$8`4a@w%f8olYS5|@jkDpc9dO5TSvfV246#a z)m0Q`vac%{>PusocLFzy0GNN~(iQCyWZj2}IBai`x!GGXY2ak8p?!Hy2m>3kk}~E_ z;_~N(^D>Uq$aE%UNap1Up}!%4Bp7w(Yx`i0+isxSQ(9ZzfrUxZhwwu|PN>4%LEBN3 zZMQG_$=m)t%&tLzk~o?3&WvPPWK0r=>@vA|orYfJ%4=hvu8%Xmp%`)jfKL1#i%;u? z00|o8YFfWbIu46Z5(6weNkAL>?drLag!%m%Nzl#a&a4^WNqX&>J|vSUaks(vK68%R zt$+vEt~X)IfPxpKw#v-pdO`FE6KX`50$Lhe+t;`dr&cITN!IOAmjodR=hiQiY`fLx zneAkY4t7u*)~a!p z>mLKCRS891y!xoHkYqmQYI|3I5{-m9@ioXG^w4(~{E!BbSO}U*c{KF#qkw3Teg2P` zL7gA1fe4(L84?H;q#`11EKmOiW$RoGt+^x^kJlazvk;jaYkn(JH(nki9uA0K`Xv8|8&1|5!-nKI(MAP*-sKlGm|8F zw*3J^LA2~QO<#)-Isqv<&P0^0pZ$ga*fiqOp2wINhgO^B&z-TDuHEmds zZI2ECsY!V!1}3aRRzmGaKdi&H0|c=}SR4h-7L-*oPr=bfv%L45=)UUlwYCkj+?I3B zlTba11yw76__Py!0xM%^9U*DMVhRWDPIxpaP;eC#Tp(}p3RPhB)(=|zV~%@M14fHnAeoC z6V?x)H5a~X-vw%<{ptpNOe#v6IrZQX?yD!5YLx(~CX)~lvvp&uY|$a_z?0#04;fP5dW=*-7geA} zlcGTYpn%i*2Bf-4_qI}RYL>DYlu+VGw5ml4dvLX#W|B0~b<`S`X!`b^h|_M0v$AeL zJYXu;wPSt$R7iZQC39)OofCqskpbU_Q9Lsbi5t~GAVEw7o$Zx6ssVNzM4W^aZuN9v z!i}wSvJU-tUu54$os!+b)C072Yfy(MoDxOxi?B`5ocq2<)b3h0UH;Nz#p0p*d8`{& zNCY?AFMQDD?I?;M0lj;nrTe5e{iyD?n_{7#FA^k0*M3c1#GoYM4HGmvEDc5|S~ra% zE$G%ifv^u(^j}bsqs{;Kv!oY?XsU}x0abaj^A*6;3y|H%K-ArYs%e%I>%PGb37W*B zVGy;$!VeWH+p^*5n_`Km<01mbM}x3|e44Bi5G zghBOR*=d5Qrquz`KOR{_wfe-t7Q8Eu6k9WrIx%IKIn9PvSxuF)$x>{Mh(ezlkVN82 zD?eM#Y9yxHj&8o5bV;3Y|lyc0|`cfG!06E0HsAEI37*hJp3a( zM}P)QcsUMfE@B*dD!`EDj;fsnjo3&biw1*o#b_u)3s7ZC9dk51S_Tn!e_w$s1;s_c z)L^wKMTs4l3UJ$EsBb!5er4gCp|7M0-D0-a!^g~1MX;q3Af?|GJJ097L`|&KyaJB^7rhK$6kN8(S;nIyeLqmWN^sJ!Y1wB#{t; z84ig`n7s(p3^I{m#~6r_o(jBnN|L?emGWIDxSmc6sOnE?Ydy|xQWes6AZ?wSdywtU zf7VfFi3-}`N9nL)t)K~0V@tr{bV_Sudb)s+tn)ywB06kSNop6sPmMh&DZ(O~CZwsh)@4Ry!|D?}(0!c#O)8m9SSJjb7DKB)43(*j} zRmB3JziA0h(olX>LNyH}R+k(gL{&)jD;AA0f)&%s3AaW8)nvJ8Mp-GLpny&(xB1ok zbOuq{@$iu2)5CsSZ9CCKiTX~a#|7f76}3tbp=zPgR9ORqr5UPyLQM?))z=+fNcX36@u;#1WJ~D{2f6urtO<9HWWi-bLZH8h%E$+a&Fq#-K#t z;?xP%kLvFuZq*0;g1`Is|IYvLKmCvY?4SJ8&%gM^oO5O(pozGWbUp%9lO~y?jsPJJ zvi0m=6J-ZAR-w+eKlx>{sSB#E0)=;K*B~N>mxS$azWL@K{Fnd5-~5mN#$Ww{#*Cg& zvbzV8?FZ09HLPwl$rz3aon`OSuj&Yej@QBn(|`WY{`ddutJjzK z#*S^jWt`7k=Y4bIynUVAd*3A8SLb_Ct}PPK%)sl+%)CCmUgw-opT79w^~HC-_~MH% zzxwW1Uw!q}SKoZ|ov*(B-uJ)v{qKG6yWf2C}P-eCqMo7fBeTk z{k`A$-9PyKKm5Z#{KG%~=^y?3pZ@93e)ebYzxqo)8+4d6^GXJe2JRH$5@pZAT?)rh z5}ndkB%#raESqJULlV7|Fw>cUS#Qxky_Aq$GJ$%W*x}U|i5|o%s>2R1$W4$oi_JvB z7lP_v?~H6lfF*WX=_GbIfzhWVT|_|GoOwC#k?>T6>0~P9900Cbw*;l40vr7u3|vVT zT`B260!ZcXGj>_nW-vocaDBT0sn zz>``Kx*av%1-$k_LPHs19Wm0m_Z}5<3B^NiqD;}enPH#>v?Mm}C{al{jy4Tcq;Vp; z7cxZ3%t=e)8cV4l>JTLx5Q%`ONZVfM0=btc0x}9Ng+`idS}IUu&@p`|d4EeHQEY*y zt1uy^X#hIO5+@v~08i+Y1&);-{1LziF`Pbseur@ z#mm|Cdr+i4RSTsEMclR}=(9WhO}g)^GyU#u`b@bbTufEXi3Cph7XAX4*wJ0Elb_{`j2{opbIC!Ctv=?%!P}9H61Ok&tq79jEi01CE)~g!R>q z&qG`$Op`BgNkXdNkAPt}Cq2TPnR}a_i9YtVb#+?ri$2$64N8(?Pl#}n;_XQVLx?+; zf+qtu(H=7pR%aq!By-V(V2O$(V7h?~ zK+b)g*WD-yM|BvLc+?VOaTNj&sH?gJgC*rI{D9`GETolWG!e|W;7{l5b|ys2#kDq)LTn$s zK!{|4p2@bBR8)c%@{+m@s6*No6ETw@-MGO{s@JFldzV=y=aP(0iuc7;5+|Lhl8S(x zY%^5rp%MsUW%!n_HVVWbYKsW+22BRy1sW@N?%9jb)LyvH_lJ=%VdDs(eW;*jW0-q0 zs#D!n(Fn@jLj)pE?AL`;aI zZYrowKk1O&Jrxf=UHSjYDyQ+$Ew74W22pVDt(o%G-hlhzN1QW8^?C z^MXFU1~&witK(73Az^S7htWH(P9(LaAxH{xB1^R%>lOGrkY zc1_1SB&3sO7j&2mfm|6@mEElvA%tB%(4b~j19nun65mKvHzNzdVu z9chYN7!(6q<3y14ss3$6Le8W7{h8Y9S}asdMv~-)sP*Tt88>U+1pGQ@gN8&(he^=p zMY1z9;BS$Ms~G^^64@wM8u$clax)`MePIMSMvN7(KpFs~3GV?N;fyA2PDB?li7k!m zv2x!uH#6nZ2+mBdl>7FaAy+WZ&eSB8ra1#SNh7sS#K_Hxqex&o(==_zJdo#1xJ0TE zDx|7OLANNoWS( zBwdkd>Mag}bRlY#9a2P-XCY}uK@%>FMj>(2Hby)01SQo^lF;LZ+RM;DiiE}8YLs+B z<4AeEQd>ZB*M>Eum^s%G1h3iUMY&Wg35E5RP~@6qoPlUiI?pp9aUKe@j&yL7#K>R{5iC%EY{SkrGuLo!j3Ekff)P4UXd6c}?<8&}i4| z6s(ZbQAjWs6ccJD#}4c45-D0z^9eCh(rr!XQArf)r%4SF0!>9{l9&WBc+Pn9MziWf z0$hHWgZRqvu+%t#7@G;*bskKUVsK(G$Xi$}t(^Nim~N^G_4kj(xdf(kYVKvJ@Qu21}8~##>689Nl24KXa?T~Z!%1AgwPOcCR9A71A~A% zz5sHpP64c?B?b(u3gBCLcH$bOB&XY31ZcwyNl?=fEGCYG79U!L%oM{^EoUtfKcU@* zjQZ?mQM*`7#aN3|JRA+q%tUponhMmRG&@VEIcCL>AQ}nTZohBeeJCI0Lu-Vk&fu0+ zSw-br+-N3FO<+95Po;M!GZc`<3DgWLr`>#As{gpN-rnZbt5g_(ss}^{={QzrH5~eR zJeq4u8m(Vc!=p{?lVty{{7)ViaEc$H)!V{g@gRh{8Bl`kKF4P`ALvb_d5p!qK4;;?bw~)gDw4Vo-!T(;||^R}(=m zkVvmg7#-zpNqw3=I{JSlh4741{0& zlAr(bI+Etf+)e^hWF*xJ6%$Z!lcQpkP+0c;$4g&BaXSM-px$yuiqU9BanGtQFecaf z^pcP)oHu+rNcVZKw=BI1Ax;$DWB%3u^Dq9>zyG(-%(-?np>;=yQr^2GKD{(>Uh^0K zf&crT{PQ1t^}R3nq}({f_kESO^3{F+GRG5q@AcbuaPF0J^Vx3~PHX=6XD08@Z}8{8 z{N*njw4FB=*vVk~$sXu}m|jOOdW69j-}&^_*I$4A^;h3~^SvK@|NB4q!4JOw;~)L- zhd=z`4}bLiAAIkJ-}~M-Uw{4e_rCh_J70X~%TER!Ag?da_db37&5ytPH~y2~Ab@!-~ZD;`IDdj?9cz=uYM8k zIWw7=o{ylwVZgiCPL>ZN2xbFkeg5W8`6 zW(cXr73_!@cnD|c&Md*m?o;O0YZP7HLCQ!vrw@xHGh6SnL>GHz_DzBNrUsopYt*RN z?43Z{UwXaqx(Z0;Dk$&Gi$sJc0R!BpGp|vi4kSo)a>O1qeShtFF|pn4_Q0TmX&ong zFhF7nN%lQ+dVZzCRfI;01E8rmc6a?7AUtd!v}D`Q_m|FQA;76;_hN;_mL@nO0>h|p zs!CGz5`xj_c9uj6O0x%$hCYCMoo~y>j&9ZZs4uh+A&G3lIolDy6_k$P?ojcSo*6(LQX zSt(7_5mm-ZhOT|{G5V#-MiMoHtoe$`Dgx%a-#(Y_`# zgYNx&&P$tjkc80;@ArM39xGtf3|woe&S#+h_4H_U$u1P(^=;pMQRPr0G|j`;Qq5Fd ztwMZ_v`Jr%9%aca5Rzg~rUh@Li4&)eV`n7q?1{^SD=3dkgKad|ejaI9 z%YD0>t#^Q!#CgiMGfFEdNpPLf)LWJ)W)j@WLI%1(sL^b!>#$aFwI-0n0_8RGjSfP2 zTQhpJn&J;V3>Ve>VTNk|!FX!`fz){(4brzTA#I{^JOTl*bbz)}IH(`@CJEXN6wVtE z)9k%QqI_bsj;6t}&WsKXWXr=!vIsP*b&%TBDouq3Xof^1>L(R$0~D%VR1+SOem*Fj zD0o;m^+F!rQrzI5wwezeCMg*kq$^wpMVlG;tu zP^$>aw;_pU5;rJNTS>iXjy}I{BNXVIaV>q$6UXUSi$0Nf=F=)QEFMR(DWW7k002ob zWzqb(h7?W`2H6eMI!Ln3oaJ5;>fqQBgYP+`Gl?abCR(pmnMkLDMjN1gody=6jK=A! zpcH1$zZM%uYNFaE(MS;2xXnTnGIZ6s>{4hh9y)O8$rgfzq?NxJ6+o_|CA41BW=I3U z2lDgWe#1yo>R+`l2#hPI)k$%;z>$*dyD&;?4ZE`iA8{y{Soaqcv1EO%XiE&86cu7- z>TbQBBubnvf(}A*g5zY)%sgUODF~3HDc@2xJ`6%#U@fyq)~dffqBPwi5u=?Nf>j`@ zHPA-Z*f~86|M@~9Nlg?rO5>VgTT?&`l5<;5u7MIg1f+`5U(z`8!T*m?eA{Y2f6UD= zocLx?{Ky89dRuAfzCX{Lrxn^SDsXo!fOlT;W8Szns$WQ5(2|0HnV}u6EFUc^@n|Z^ zBYgnE3{TGS@uQ@H`B$nwKsA$+*hZ;VN0WdE*}MpfGx$ zNIhK-U(KAEba_HtU+rEGp>Z;jH21o+IWx+_&)KvBj5;xpGjf){LjaY+FiFsC>ZF-I zgc;F=Bt-$eG`G7_2Cn>Y8v-1CyB8Se^}I(@5{z@{l2$QV13zvEDCpU+SbZ%cRLZ7Q z$nj|GtG*Gr_oC}7#A=dM+bwo@EWTe!v$L{~-IX-MUBw0%5yfu09>3dL$k8}3QAy90 zp>R%ZTD>VZc;-_QMnXN^t?m;LnHd#UANtTN_x5wzwAJ+NHA$)mjTH46*Lf-Cecv-O zQM6iq+-LPt6;xNm_BqElBWcFD(g=1UHIJtS(u~XM@)KxAO+us=Zp{kx;n(oQDNARN z@4E?Z(qsn0>E4`n*UYSg9_4Yn(pU{Z`aJ~O14+{``C6~RNy~R?NjV-q0XLtI4!O~E zK2Rgk2_i$`NOtLM66^NL2~-j@{vMVkAZ*9%$*%s_G@$yn6CQoGh7ArjhfNe3di0d5ZQ7H784bh%U7%MzA)c-jSR zLQ(}*97r#Kn;gA*3JVaJsoFLGNkZC9cU2tJwO>!TjAKO;vbU^YllU?xzULg?PArW^ zxv2%W`ZEYN>8u_m@s2|+d*kG2TpWY5_H3upAs~~qBj3-`h%#b$7G9rmkzbw3pCKYk zHm{x=lLxty2qhLk)phUfn87N)03ZrtR9_uPq*0?_!MauoF#FzjtJoBj=ap**jdW&= zu|}i5*_5CR-}2h%SK1i`NQT#Zy6@*bjxfRwBhAU4C?0imQN%E|r*F0FPe63mTkEtV zDRHDZitBYDPqI5mJ><5So*CJ9i+`7ckV0Wh=9F~wNH~jKk-j>NXhf5?1wXX%6!Xk+ z98Ht9YFDEh>oi| z%yg&LpyE@)Ml%YXT1YiDC-2*i2n<1#^u3!x)M4*WSzrQC8%L>PZ!Nf)vWTtM$=%I6 zsRf{GFiB?_I8@9{_iBi8P!$rG;=eKM5yzpcN@r9M*H=P@rcDnieEXv$yQouPDbnfI zzvAwpuV{L9V3CX@kQ2^0(K#eL;frcIAYYiJA3Bo-wSP+ZBp&cZt_1kYl zQQZf@-1nvKv@34f~NEkp{6)=Iz1Sw90G%b^E z0Z$&3!m}f?l8u7k5mYCD@64F1pnkFu^NLp>i6rM@O+ELe*coPCN#dk+SkIv+dWj?k zMM)ChTd#kO{>{Jqtv~qfKm4`7`5SLbovQ#)28Xegd^)cqMSOYqNB{Hx`tN@4r(d71 zlqfiko!n%fj{+PgSr+K$x-yMu2ORVG(ib}BGPBSD*mlIan0;%|gxL8d`NdzEzx?y$ zXV*?}u9BprqSx!3IiEg#_0@O2`}Nn~{NVfF|IrVB{G*@zuK z|Mqu(>$iUE-~9G(|K9KX{vZF*zx&yr{t3T)b7AHMuNPl}!w}~_@F9z_^9(1*Kp)fZ zrYrd}oO}^5(R61zN-jvbWaeZPm>h@`&8u&n2X4%~MABt==s^-7Y0jD4H&Q&nr}^Zn z65D3vw2rOvH9F4S;xY39TMHqE zTr_Y2j_e(R?M6?{?hGc3PI3`2jVi%}D9V&111}LHCLie$rGFq4u~!vOqtv^@X#ugB z`h>9kRs&c@%!AwD)#7aAqCcaxOMn)?AoP?a2BC&A0!an5`%UEp1%jrWK=YXbED<=H zHn<6M38H)^BnU!SH+Q61Gp`hNO{TeRjj8B;NKGV-cY{!tBvZx6u{4_(~6nb*CK698crnJs@- zb)_GONNhf;I8@x$_-!N!OeK|uaN{aM0}vCO2pSBk6BTiZ#5wAsD+2AB(S$+H$74|! z=`8WiXe6(_h|(yUeE&{WZnx+UrnkHr9pbWntnG%u=_QKVUuNmOr5?niaKAbgKbM?53n zP8QII7~3VFDSV&|6Tku@Mnlr) zWg|4xIbUj6Nim}dH(b)^YXjI4&&k8|hIf09Y8S6kjl^0~N?>B%s_h*K)}ff0w4Qxe z<$c1b;l_#9sVGJ_`5MWVbOaHgNcfKAL~3D~-WcAI%K3ov@ytDJSyqbiPy<+nF>4u; zBV81T-S$B|r}u9?l2fywMEY zprj`c2;lUQp6*sNQVYgQI^+w5vAS7+f!0~EH7z0lNeFJedqBIlP6UUR;{#}(zM=TP zOS}?*rN$B2R{#=*Bgwf^xJQ~%NwdQaL$%R*bUivDZETu;F^CCU`XTg!*xfzgP(J|a z6$iZ~IY-SyG*S(ckZvge3F#OTkE{+R3EDZv#OIvc&{}Q3W?cJPJZlDea5YL&a@q5B z_Q&@&Oe3M`^)-R%6f7>F-0&kDpf`atOIKSP<`~Eas4)A)!klx<>N{oeW47_w&%RP1pj2VeI$vNldYUD&232wyG(T7=U8I$>V-)LW`!h2@Y8p!Zlho^1qmZQQ&FNrDX+#KS$YtT*<_(PG z7)Zvya=Yf_zK`ZcXr$0YYKCh`Z-Us^AZ?*qvp|7YJJQ8M0%%C~+l0N)sewJZxrf)o zW-4EKCe9cM4^+Pnj0R-1X`rBAh>6}Ex!-rrnFifa1vofF5?CRR#)}Ho*kSZa-iVfZ zuWlKiv0cwC#E%Yk5!n3d-GDbq{L)Bce;(;B`o?y3=X^$eZ~7dJFe7lT!e@^(uQ4n< zgn2AnwJA~(_dEu6>P$-j_B)iDG=q0FPPv(L($y0XXz9q3Y>~@|AZdj27UKXj3Z)K0 zI8AG3U=wSNxRK`Oo_S5)Z#oa|HEdKLh`K->=iziwkq+)6Fk6b0ZQ3L8X0nbFhi%F6tnS za^E9GFF^X%M$YFs6B{v_57*Nm_7zVZQ3JXil>|W%aiOS8z^g!V3vaA9%U83BvJg*P z9uyR%{yuWvDCTIkm{j_?0qS>%QnB)WyBz{U>>$m^Yy3o-A=%lGB>hkiVu8SL0?C`Y zH8?(bb$vt*G!Rnd5P?F|%acT$5p?2x&lHrqxeyEzq>I8ceOaaocsatnM)pMzDVbI! zNf!*SHUJRbM3VX^O0tr9fY~#9gH&41tx+8s=jGhbZc4nq$jC|6|CUcSEt_Kk}Z#MwMgrY-?w;ADfnUZr| zhL1~|evX!IM@piJ>%T)^*g+rxf+T4+qn+{tX^|%>q_g*tfP{#bruG_*dCw>o^6A88 z#rmA1^Y(kD`A@|pwXamv3OSzH@+Cp=n&WHt(bmV%qVQe?^il;zcWt(VQojyomvIw< zMmn$0EqLdQ<{+P*7!* zW)8WQRjcE#gz>Z?$lu<9=$6=n^-e^1FRs=Y ziN|-q3?ur0a}t5hQ2eK|Ms5Nu{W#nW7#!;_Q)c3rnvG;gh%c3t>+A`>-NIjNXu3$EJgbY)JAY$eWkWK|AT>CuH58_-wC;Dtgn#6Z> z6wST=+duwa{@eflzx(PtpML&}J0d02s5bdDpPIIO_cj0L|M4&W@jv>|{#>)lNiO>@M7B$&wduX@83MDVRVa1akOM>tiWMR-8X;kQ0e)@7vps6A z8N#*SKmWyF{`{9e`{|!v{af2aL=1Vo^y&5Gcfb4nZ@&KFPk!_pzwsM?{XhE6zxLPv z+F$?8-~6>-|H+Sk^rP>7^VOGMep#iyzH@%~)enC7>p%E=|KRVO*MD;_zxX+S`XBz{ zr+@Uvzw@vE^}qTT|KD%@%m4THfA@F){lEL^pZ(dN6U#nxMz3>@f*7QUfiqlMdqj$u zoWkvl02e5wVzP8h&VcmfY@lRjKr@}*z2|iEqNEYZ&>0eegAnIakpzt+<=2B+d3rAe z+6p3}-j?1YmI;>*96jNqAJlWUga$&Xpy;0P(3+f@66ms=8V3;rWCeMO?8eOy1`F<+pTL>z=Fdb57ShD07lXB8?+zMkMG~c9;33 zq>1PUy`0W)ssb{a8$&v}N=)dWH>=sEA*zru+Bw7>DT%M!B18mA&0Js62h2gm33cml z@l!cvto>8dufzljuq0KDh&0!Cr-e!J_9O}k=pzj)yopdRNZf3^1MerhiG)|E8yGFw zCQKV85}@KHJ6x(;X+FF_nshMz2#T@VE2Kvcs#-ErN9?ZUfQqY4+Y;B_WYGbNGJlHzkWrZpv0*C7SxaR zgBK2MUP4}CKgu)S$~|Qabq}ZjQQ=bY@_h6m$j48dVaKHrw^?FR3P#yGLU6|wPzkJ7 zUNNh=z|4oYg|J88#b=I_<+>K4tR{mgdr^aJ0{v+1R?Pjg!vJEsY6lfJpa`W?a;@@oR&O<59%JZn?++WK&I$S;gAO~8nMume z;!(%;DTxlpsOk*83XR6T5rg8u1FBv%QimDAeh``x@4gULq@iIn-F7Z%KE?|{PMEH$ z8HnJ+cv`2RcXqf5GXNtgYN!i12Jtb{@{$+IFp~$uqjatf5b7M*gOK(&60+3kv5zkq zjq6&qSAp>VbM-DiyLH)h*Kf@Ap3inw#V*@bzLl$7v64V8G}1sc00E6OK=~IU@o&(e zM~?;)(4tQV2?@~v(V!I}A%UETm|R@GB(}?!vR%I4>U-80G{)TTIpO)fbI$ud&)#d# zIp&y`HP_nvfs&+0V4(SDc1X1(Tw>0GFhQWG5+bQrEW9KDxH&AKk75GBn<41j001BW zNklERBn~5y6qCwRSc;0Mks)!d z%fph;gHzwRxw(7idgXUCgxVB%&$Ih#O(oPm;&tYvOA^u$LMvfOY_eeE_Wndr5J(_Y zD}zuAr)?d8t+4gd9)RjlZocRk8R-Hw z;hJX$lG2eRLM>95v;!fMWf7pno4;xu1exe614pf0)9Q_W>~y8g&#m*4+=^MMPc1*I zWvoUUGSbpdg#M=f`b*!`yEJb-+Y@SceGQMvVE+gN2&J>D~5>N=sf0C>P z2?_gSLMo%GT5Z7lEviYn3JcN@J8oknb%PCbBu$cP-Qd^lG}Cug6Dp!e#7!%!+wy=o zI+8>qivWK`1ydxPT+*Fp#kiu-^BtV;UbjHi4ek^6X%MVEdV|$R6F*#G+j+p zY%R4z@=>S6^oITb%_-yD1feL+rvVh{yzb}c9E^QiSR@u*4OMVSo25+M5mt#p=X9d5g=rslnBEFs;IolXBdK>>;@hGNR%H>-Oq+83L2BdO zge2+%k0~YU#0puTZIVqNGZ5A*2uzpUl+e`yRT7uAofwq0Yp=SS0FitBHbCZxM8ycM z@0m%XIWz(YAQFibXnR4ExTB|=S*0%G2D+4^P4S^|?$SpQR3es*iHh?A4vC{WZ3|{V zg1u-GT9}2m>6gM?Nv<>OjUo~cqEUoxP2C0@EzWdZY0HkFitwQQczXsjyQCGAdLj(N zbQkYX%T~Emw+LzE&hyy7Wru92OcJX7!UC|-(b6<4C5RRTl^eB%QpS|GxBV#J>(Rcv zwhdzcl^mnN87aC7>P<2TsRlj+To!vWe!V!Uw96D! zg!X?xi~sv8A<2k%`J$4Csiy;!fk>dK!#RyeUGl(oXG2O_So@J=dpHquCJr`eX7-4O z&p>MKksy%<+r~7A2+g(`10@5{#(l$1odBkotVtu}d!KkEknCtCzC>j8DkdvW^2yJj z{)OW?2j@vBm0zuYV`oHyug>HqH~o@ip?N#*7a#(ToFHnr1R{;Qcb3Os#W1NM18>!U zfFc&_4MU2^)6U{i0Iu~=Us(!PkT0NJPZbL=_`6W8X7x zB$>t;if4LCu^~#w`c%`@5jOCuY^0TeQXMVCIC$n1NuYOy1&GhQISCe7tnk_Kre>*T zHPp1gO`;I${t*=8D42A=mk>nIDB>Rr0A7tRDE zf{9&0jNnOBDD{0T(nz;&X5r{RiFVy2UezRmM%k;zzCgTc#HEc)H19WajS``k1I_#o zcH{ygk?;+4T3|Q=xV?Ns1-_SN>}3`K3`vCQ5Y1E0O!5kIo)c9lr3{{M`TXPyfgN{(m?xj?jI@Hwkg>i(p^HTh`?TjvL7xBG|iC{30dSl~H{1 z&YfGwOyZZJuK%p6N)6BN{Ht%YwrG<$T#0xzyhZ2j#5$as&R!GTSf9V{cfb1wzxUnW z{pDZzh5an&vkz|{KKsER{-H1b@K?V2V?Xv&U;o-qeC=yr`}$9R^{0OFhrawnU-~Ow zI3F^@`Ak3hl`sCtkACrQ{q1l5Cx8DxxG(+LpZ>+~eCKz5{nvl}7r*t(zx1tN{Pkb` z)!+H;-~E$6{3E{iLi0BBedkEGz8NG*L#*!n=={O2&u1nfNi}9We0w`uV*`+M`!mH# zj5KZiXGCri>0}QvL`E85V0h4QGHR3hhWVSPj|IE6^=(UJWckM=c=1* zxC)t*MuPy8xYG(o2{V(f`tKW|Gkl?-xSlxDVXl})AS%?AkPfN8xfv+BpxKRprC-4+ zrKbi+$|gO0s!K``1c|z7dbF%TLV+=Bn`pZmL?Km-Bq)h0%|-?dkuW2SG_*V%g~+5u zfH(?Xc@Vx1IlaB=v15|T{JwjjM<-z;;Z|ThBg84Kgc34a2RYq^UIT~j*zq&%PZzY| zUPfX9&{kw3srG6RH1M(%?cHE1Ka)&`KHGrN)bGt2pfhFV&Jl6ogs!4pavC^9ls40M zoaG@v1Ue9smL1Cp=tb7FVJU382$f>}UpXX_q~gS={#f54Y{g#lBw+*&r4(`NB%GYl zy|0-Mu=VyE}AMFA%VgAvR%>E?l8x@2|bOAhYQ^LcVC2wr-9Sh(^4~ zZ0Bb=6v)$1-Zs~>LhMKgWj3W=p6Vn<9i=ONNKkJ`W;(wRNeEFAJ!yySGZL?MHJxe| zltAiLfzF`<#HNWC!WK?5yw}VSh?qRk$w+{Mh}JUA3Vlf%%R=6J`_xXyBzX3!b+#}f zFb?5iK!S4iN2jqOYGIHhvo}GY(C8q>$*HJ?2?3fn6S2Jqut&keNf0)~oSD~zyriR3 za)Lx^iKH<*C-DiD8C0JIi3W9}nlOod)-we_( z@<_x4Idn2V2_!SnNW~n)B|}osHboY501^Wm&y(b8M|a3=)9)~eG;z?TFI8W+wU@9l z9d+raVozj5_q{OZx9_B|qf873(ABM8C@~uK_9E+OM)yi;dDLeRc3bVp z06BuT|J3-?z_E1+ftk0&B0T0I7?kl<)LZ^T6o8E0k~C@*rN|l7hxuTk7!jsr&q+B4 zG0dorj*>)3njD;z0!-J+Tz?$BmlF*N$}-&o$)X1K4M~*7=DiD_i}3Y%J0W%kHM9ys zd2-uM4R6U6iKLn20!b5!L$@`Y2U{G!%_OL{1ub!TBrCerBHwe)Xj(3zIV3&lK0E%KJoNOS!qw;y9G!{PIVB|4oQ9x8qS3cv3#h*h zBt+2X0tPpu*X=cmqj)s6Sv#S<`_T85^9&k`gl^clfOQfb8E8AIP{1+JEiD#1Pn&2o zlUI|`mOn{y5~pZ~QS_v@<0QE=4Xzji&>eO6WwaH66Zb3Ug|2;~h?%JWu{j+8i3_xp zSFuP&0wX79Bxv*zL?_1L4FN;mi_(gtX0$j`OO5A1G8#Kt?wOd*7wl}*QtL{!8UiIV zT2mq-H>5Tu5))-4J*K&!ILs({SsG%b8D51erG+#iaaO$~9^Rr5CWH*~IJxjvPY|iH zS~5vbyeXb25A6ieWS{y;?a=v404F_Bov5kjLS`=jk=CEgeg#H?8Wfu!0#TGxfdFNQ zodl@{K5Cs0m7k`=vLGioNhCEnbJ!?`IUI#X(o0*J8OfGr9}oA;0RdX=M20hKD^&l2 zo0d%gmq-Z5grE^hR<3$~C9JA6rlqJOjWiPW3r(t!h_BC4G$Y${BVk2oMiC9)YBUC9 zIBvsMd9wY2I~rm8GLlpZi^AtfnjsAJZ_^#B!Z#Y&XS)*ew&d$Vq*2Im@-}vG&>ZFx zP4A=_$Du`HTKd#^-PJp$(NpCl1)x92($>#1B_&*u4w9F%$^tpG;y9*ttVgHuH$uF* z4=KzYNg{4WeUqR_+2}|ENsb6HflOTCVMNkdW>Rz6*>=6wDe6XjHZ6&ebFXwHMPYjm zS#}u29Fl|sQe#LCreQc4joMiRj@I+S(ut%uL_am5g2Yu>u+`tXO=R1Qrkn*N!ANQU z0YtqsaKz?$36T%YBQU{m(`R2*o*~&{1(2hNHu`C7Opv0fBSQ}RaK91jwQ4yi=b8)> z$M@E9qP$88;s|AehMVdsNg%=yBq#0qsG5?I2+HfS&ItB7A8f0YKrP1=VietE4@7{I zz?(GkErUQBCpmiSH$;>rkrE_J2WM!}3E*6$@?oay+%IV+F-9kGi;RX4F({MXht$Fe zLKDm3^r9kkj-V#2y}o^?!T>>az|gWe5YI5|Se_Xi?r$#Hkm$Lk|Z?phLq(UU2rD?iNNXo_SlJcO@ui0xFbch z$nnTYXE-ns5{Wb>34=zuLhKW^OhTe4HN+gp$!*LpxB&_{*NK`+WjmRpjJ7r_FbxzZ zVRRh`kz%v+$A|zEX>+0+!H^f?SRBRB+s`6ubx6{**O0Ej?8q{KId3HKMaVg4T#TKN z$9g4ASW9Z+i#2ZwnG3WJhMaIvmjVD8H9;niAwCCMfA*CmwR}Lr^Cl3mRPIsDRVMvi z`+@dz#1jt6zmK2J?haEip=xb^Z4>0?O8{n0(qCgb{}IZFco{2%$Oer^%+N6_T{KA> zi%(Cyh7@klsq%18h;-cNv)N9>Z0_pNAc+`837{d*QmA|P%<8AO>9+E0DXEd;L6Ww1 zs)^q*MBd`QZ!at(ZPC+lB)ivbd=!8lK>9`)HOvOEGo#iGXHH0*k-BZL>YZtcN~bU) zNHs()TZ=pdh+3HNUcxT{q&*NwTn&?URB51&!|4IVp<}Wn1Y+tV(uix%nXsSM>T8ZI z2_nRzk)(vA1gNFYsOGM>NC?nCXOctDR6ohEP1vwPin>+h2Fb3yLZrq2Q@DU7c~yJH zCqYw^=o5Y-^u%NVY_Us-SaYO&07)TJGmYvEDuC~z0@=Fl|0iDF{UrqE;T78KLblY7 zEfkS-Ox=F5@%Pw=nsWxW^6-%xP+f1 z8kN<+#uEYb3El#BoPioaPKNaTo|x(uqQiFKB4CQB`k&ph24K$IKARNoy($o@6#)?T z9ZjBfjJMnU>%Isgpf<)zW^D@@L;qxGA4`c~^Op9U`W*Xzd^ZllA7V z3IG$Q`7V-7^Dk^1&fARb9-j_Ti#q_P##a(p2RDNZ`g4jm6?G`2O;*4g!0T3J1#2vNV*=3iKGl0=Mz zpW?)OfJkPa`lA`WUa#ltS3$60gkWf;ENRzBM6z`wy>`;LiBzj5B}^C;XrYcBF*>N} zCD$B|xorN;M|%}gmKc%(&dk=mFvzGxNyGsIpV;eN9~3I55{>f8;wYrKd2$EvPKynS zh~(X#Vvys}2$B*Ztelnt^yx=Z37|aXL<>Txb`v3qG=8rWB%#Fz5^LTFqirNd5;(Vx zLjs2q3GL+)0Ffo|Y%|&=0{gk9c?ya2o91^qbA6}Z3`*3z!+I~B_`pTVb*7C=EtzzV17Q!6Aj14}~Jci4yIN!ZVXMbW7DsP+BP1hhQKpk!fNX$G4! z6FcN+;+JZs;;_JSof$og_mMWQNg~|!G z1SJ{8%idmUGgW(tr#Ki zK-IQ!=56TvjAPoud`@pb^L^Zh2u8XID7#ZvcBa!<2lm-OBOI%bptLmyv6(2TaB(W^ zvS{huqEn4y=WK@#>E;e1pU_qSP9z-@l-8fCcc2RKyc@t~Ms!N@zCDF~g$-XOV_6^j zAwuB^?~;?PF>*)e+6TEO(jluLnwt2*9|Y^K;l^j{R(Izzy{Db`FK4N>yYGL-ewAxRJo8E7wZV#`6m18;u_6N zd}m~wy5oAv`o-#`tF^|?E3@`$0TRtFB_(b`{4`37;=D^5!0`6YpQRf>Boeg#(;xr= zC6*>gO_)zURni1KSB`<{G&W8qWD!ZC(NJ7Q8XQEp6RCC?032qLuCWyjXnxeBO?;_e z^wW`7O(Cg8<-EsF8#G;)WSG9Ma-cWgp*4=3T1ZC;1<=>h8%Wy9B%Q-LBNRH1Evq6h znh6(F*fsC^)|x7y%Ip30N4ahZ>BrsTL_wXX_Ooav?>DqM&P5|h zbL`I>7jK^u9*~+@Bsw6EPnC`3hdXbsEEBs6XxmU-2`g?$%_v0~C{5KSgCBQpnV2wv zedq!aLc-|-mK`?~P4TQ$)b24v{hf_!aW%1!Mll+>W|iEqeW3P5U5^naNN2+!1e4AP zN>BuBrmN>xiHwo;K*cjc3zf9iffnk+X5>{hy>BPE;VmG z7N{W!#pK3nJ-t%)*oZYdP)8prS-;q5+3_XHL$nuH<76ZnVS2m@`$#D9{w%c*BM}z2 zyc_r-Od8?Yw>znkCfz!eIMy^Y5E8qDB?OYpbW&O^gUZ{cvLuk&M*F+q$&Vd&TK_;l z3sZy&Xb(z)vhU2snU;bp6=)s+!H%L@sg({3e}V1zq!ZUV+JKqUzJk@VJ1J6XEUls` z4-j%VM@i=FeC=pb7aqka-+Zl3=OszEtF7ead*;Lut!x7ldf8{jJFM>O>NIZL=|dRm za85=edHobB-I9wjl3qcIm?Vr6Zqs7b)M^_VPhG zGu*TGX&r3Cb0K^D%SqyyfuJ%cJ?dVUU(cj3 z1f+Yf1f*pL2nBHN=xv?{QQYf!la6we?qH*R1J4L?uUk1wLc;WW0MecJMlEmR8q(Ki z=3eWG8>o$+@)ls{Q&gf%cI&q^sD!tl1p;PN*WaC4j<<+nCUyMme^A?;+R6vq6uGc_1_rB(wQ~*_90l3qc^`?qSNV56V zVaiA};LN;jkt5;9Q3OFFU(cND*O>z=PlTNujvF_XdOca)LH!2=PN4knb%(TZql5s4 zVbHZ(e|C#yYMl{IkcfAlUO2z32wouZMTk0%+3fuhqq7)GIaGhkS4D{vBJPt~I@ z!fiM=%4j;Sh#drRlF^&9^UX$hFIkSSo6TCN0(sU)nyte2MF-9B^0SKX+P5&(C?t@; zM42S-KtVSRA)1}*m6fyZz(+{kC8^t&MPHRqlW_ccGX!D?rjz>q>-BAJ9G)P7kgJh8aFmS z?LaNlj&`Q#U0AKI+gCF-`h(&3-=7P8ij@_ICn_wFT8jWO~HnQ^or0h)yr1 zbgDe4h?Dj9Jq$IwKpdXhG-#SYo&g^;CBn4S2iO$bUuvZUX(^RVZ~m5?*Br^)>F<>j zc#k>zZcpz(2u!0QD&^2~-9?^6+nwx2MOY-~YX?KCE85wG-cf#}qTXKI z0+XV)I`=S^m;nV#J9kQ&XonP;**onvo(KpTre|044zDF?)UQQYRRo{NKRqfz*lkkJ zyY=_D2svJh?5U3yS^{+JO7Rhggc#`wYRjgC>2emkl)3{6F~&2KYb1@_`dD(pNGtB9 zt&oUm+|>X{o@-r6WOE5UbEQ@imicsB5%kDg1_H3vZ3vQ{Ql3vBtfHU3k_^KPxF_~~ z0i)bdLZE&A4ryIj9T#4x*7x4gMuvwaO{Od2qMc1YIFc>}?bt{LCHdaxfBAR*_TTyk z|KLCU(?9=<-}`A~eI~!@ z3vkje#*7AW|M1WL;>W-F*Z<4^`oH;$7k~PfEq3SDx@k-#q18>lu!C|6z;>a1ygnq2 zT?06VbcfLfY!NpRfqM}|Ip&@VP2qbf_9)1$L7;#HI)S->8Jj6?RHk*CBrp<9RM-%; zJElz>NJ`$`=1bqF|KdOY&wl0q{eQmnVW*MnNU9UjLSRnITG2Z{#v^SJq%&v2nK|7- zJ?E|6uD7={=QBh`Z|5*?GsB#jH_l|vIine!ft=4i`^s0pnzs-C*FXNb@BYc3e&d@z z^Kbmkzxmal{E6@X!VmcK*B}1=@BivA{qnbd;TOO6r+@za=d-uk)7SO{bGf!J3D?Ob zwl6mEy`LwSYtb`aJBhfTXDhu(vR_Grogm4EI?V*v-SJJ7(M0auC=d)XVdk_LNfD;L z)$!vqvojwCA`vGvlLg{(PXGxd7SMhcG~PPz=_EZ3k_4f9d5S`ybITAp z$gUSy0E&qB&KRJ?Pg+mEvd>{rS>54;Ky9TF9ABd|v&CcrO1m4-&>&$vAoeysi8cp= zfI%b|w~Ziel#8TbKAA=WbgjP85T1H`3yvhwj1(n_YGw8>8p=$ItEf7I&{g;P)EJ?E zBuW{?6I@b{28xTo`J~m=O}@23$h;T*Wq7%lfaVGA>bsASTXFR*NlKQlr19o?h2ZV} zCHAP3~x*QM7kjbmOuDYVI~$x7W#}^?W;89RL6z07*naRHe;F zwRqoWHZUS`M|yQ}E0BZ<3NpleAQ2?E+19>*Iy4BWktuCGlUskJCsO9 zBejfq?3GB{=ogv&3Il2l)jH|dz;C@OsqM1F)u!hDxKX2FESbHW`y})(tMHVY8k%Xw zk>=J=wS3+!qE&wDG_;qWOQccd14$DAPm=VGlLxj4t}|zp5K<>{H2p3%g$(QaO4`|B z$)$dx`mZ`Yo?um?WTaQgn8m-wQo|DUk`5247n3G9Cwqx3$sZOV206^AcYjj*MjD)G zL~F^K>yZa`ITmRhfKSw0ChhM^qYfG&r*0wPK#b-z%C({wwNfPKo=OvB(lEg!?kAPw-_q+i+Swjyth4~P(dq4wEPk&Se=0U&-c^H@eS&*VA zr=JNrZ-OGRypoiZo7zty&xRNl^bj7IX@o|SmbaRPm*6A~YibZ8UWPx+!HQm3m^{)hV-n0P}BVZDcH*XcoBDA3+00Ts>8TI(7 zf`SLpM4|VO66||su6@oasoW;M`c;O}=;eE+k7syL$sk!*n#73ihD|GKQQ49Wd%mB~ z8PeH<*eIZuy{7qMGTrC^ASt^(E<_)D1{dDeV^F&5X3fxBQHQA2);Vg|t5U<^{b*1V z{!@PR9o+yCMajE0lOl8NnN$4TF$j5Z7D-xtDOT4Ev<XbmP3< zliP~T$=rdbI1`hdt_+~v({NnnMvC)x5<@0O(3Ou?VCW4$0D`35awZFf&J1`AtupPp zb<0cY;|icGg6UmVpGcg9+PM{S3%`j(Jc-4=56q}}c0}I$SD`mEN&;%C#+ep&79y>c z?rxdr{|&Mx38N8N-ctvxkRv6n^U;<>=?t!J2-3uaihyvKggLS=7>yXX(sWx2I8IZr zT8%_ZL&D^#_j+Azzm9B7>SYA!cDAn2f~2_P=LHgjL|UzZCfVW~tRXlsMMmY0o?N&4Li^1bt>uD-PIG(j35 z@!BSl=ITl#KcI$7&N&D>YDc;aB8RmotT*=@0+SJh%$(fUGVL7{cnB|8cV2iTIeO+h z`w}zb+q~BcfFAvsr7OtNgFEcJ9BFI;trLrrcBsiH!b6YU>EkJY`21tLFoN>fY^*$ zQ9LAF$P!Qj_0`#3I2*2e<+LoaA<;N_(}?{}dWaJ&^}=hS$Grnu!Y9!t%DK7yezaal zBiqwOyLLMq=_DwYxa|D2f~N*53ZV%X&_f_6I$)K_l0tJ9uvOMG5&#~t>_z?1g)<5{ zC`XmQ300d6Lox#ArJ1BEdm|-YQ6<&>16>JMItS;R z!~GnzKH2BE1W_J+B42+j)1<|Xmnf0nj|>Pkf60U!4eyK274p5KeisG@rFEDZ0~@Zbk!#0z z(k1zpAI;e42_8_F*8|^{gic0-6nkavZkhnAaMh`!tyz}pTTiYv#9)K0K1Kp^9iT*T zUOe_9d5oA^(Y@ZjRSyFiw`H+c)JBF-yPNF`Jen<2GBX4kisFwS3q(ktCD+;17Q3t>mlbgNjVk z?ngvw#m8HdXu+r`&9m9Ur{nFxv7d*S?T{UViuK41TDw*&Dr`(2Z(0N$X{RJL>iamm z)A?bJWA8>|5uVQVb1)c@?Yo&w4CTnO#28VE&KX5cLV*+&vhXLoS7Pmy)e6$a?8*c~ z|1_GFMw4Wuo^Ge@IaJ+{BsxqIw2KNE)iwgs8phGAmx}$U{z!#*!sI8Tuf>gHGiV{UeEOyz6CoFWm!iMVttB|-M zef>%+a!P&2;*$>GGg6~!ts3ffagCMqgAUNEyqDrDdS%f^U>>*Kef=(M@#C3et{|Z%YIRVTY$#YMq&OnB&)dQ8ab^kE z!<_aqlEBD2y`jlz)LjL|bYEHjoRzof5bmpwFJ1}I;*^F(5RoRy67=DZ&<3%YWIvKN zPxSaoP#zvBh-ikkr8<1dz+!dHC_$R!O1WmWw=<-^Im%Nhn?41LR92hN$N<$NNJTzz zf*67b!gg4abtaM40kF2JXjw2i4M<=gc81+~I_7=%Tc%`RB=@foiNGXh+5se+5X*&9 z44lT#KuQ6TBs+EOt3NtHl5L1aEk|7Hs2Ut%I5DvxNu=H4({_(!q3cLN%6;|~=EQkt z2N2rak0d9f^-VD|*!PKO0gp~3!kH$RSQ!}2lmQNBM(sK!MBUhKB-=H?~GHrRfRD2{E9Q- z`emEz1iHx}5NFZ7B$vqb<-i2t*R{_qy(H<<^7<7?^}I0XbuUkxJdz_?w_Q9L;?K)A zNhSX7QdF(o0HcvGALjUJ&&K&|y1;b!z8{E``8H*}wj?f9-GlwV(O=H@^DgU-{A(zn{a~+x+-XedWi0>PP?WfA8-O z{Lvr0e)HSE_6tA%Pk;U&|NJk0>s!D5?Qj3lpZqc5ZRYKKHk!mUk~2C=6rq;jnm#@^ z9!=c5Z%wCuax9B&s57UJ*~CD+c|Rb*k)-yf!Tww`a}EO6IV40MaCHK8nKab~5cHs# z*iYp^K8a2NHTE|c1W9y0`aHtNu|R|wZk!8hyK^*DVu^J2qdWojlb4kKGl;|@b>8}~ zv}ZKn)QJ*ArZ<27>mN$fIYp=RqDzq@Ejy_qt80pd02L3OP^Y)IbFb%pnKm)4vk4na zB_)1`?c?zMwVKcal|+cD00Z^qMae{g4VrruDM{SjArVUaK4`FJ!e;-qtwI=0=+txZ z49PP`k`yPiGCNT?qnp&+c|ws;q@yj63t*K~>UFaF77$6LgrvLE8yp0BCb|kq3X`C5 zG=UN_Ehk#`h~!y*Zk|$w3K=A6Hh64ylXQJYyRmh9_l1!)coC3di{xX`70QW`dS#7b zXQ7~-)Hv#W3DYttJtd?=XDv$V>{SDmp8^u8+VrwUE!{V@FKQK5(_~8Xi(k^rOuTnQ zRV`5BrXeJu5J-Wm7nQS#&zYoIvaAA>M(9-RvsmnFf0_3>TuSWHAljhxFa+wYWl z#2ezq;g*jqDpC^rwl00(2-|CLXMR=4tF=`qC1h!QpYO;UxG)2zxa~MmprN9bTup+& zYOvX8idYvc!tK{;i!`7PO>tuZh6I&P4^Ko4?2Hmfb{>ti5J*KsNdp(z_&~ASu`LFo znK*7?jX^xbsDv{U(%MH-Ij5tT;F4K-Lk1KOP68*@eZ|>}tK%!_RXW02S4kWbqz3i` zJV!DSpV)zc zYIwl|biGzP#`KA=Yc)OLf&E)bny9sHTs2hzk34{q4#Fx%uakDARUST{paiKYVD%0t zQYY*JCAIjqGxNb|ds79ksbr>)vCTfUX^Gfho-Y4N^NwyL(JQm-6D>tT|$OQ^)aro~1z$wt&5VdE7EHFTm(hN&V+>X@UA zvuPMb4b`cr%KJEVAQZajRj0oRo}O7_1*8y(8Jwp7B+SfxT%lN^{@(*!tkb1^0g3Z) zp^bc0FMhBPGHIu7QYQ*h(T2%wBjuwhr3abv`yFwxGA3jWNxzLQ)mnS;oicT>!`$su zY(dj37$7$rOxH> zegx4OMHk-0hOC|v=uYxpmLy5mzW~tYnku*ltgev7lZ40UJS?Eywx0OOyXgPp>Ro#E z>%O$C>mF;r|AGo2LKTOA9I6zg3PPd@(jdl$9b?OepGFfpd?liZ4HGn?2INpm1PVkD zp(rVkQ&IIkb6hs=xqk0I?!1S+pJ%N($Kf8wHRoKfdZ0z1cI9RB#!12*?`>6i)tO3x zAdwz!aal2)#N2Sv%H~Zz<(uC~X09Oi?co8MP@KH@S*wq#9&bLcieu7#g0I&&%M+4s z8=^|mYG5WwuD0!WsX)tCCVO^!n3KLib`V{CbJ~!Jcim5yr2E?6{n1Tp3Q?xt_1;8~ zuAp}Hc^?qusYo&j3zMpt;z(zbj4&k7q^sY~6;2np#y(xv5;a0zq*QKKTc+Z=4)==d zM=X84U6l>6S?2E zj(a8F>rHbFfX0PqDiwBlW}DZ=%{)CJsdEL@901>35fBtpAKl{^_uCA;Z6bxYl@2>i0O~YmDNI) z7f1+fXWeyX9{f~NBI<|aMF53S@|x+S7s1tTT8UdJ%j-Lo-L0+F7o9EMi`e_fd~W9X z?5^eS`kC?)F=VFA=aFTbM!_q+nXeH2zFmW30pOZ>+fDr7u6k2`Oky`@E$RI%VfWl4 z;g)%(c*yHuOCFbyJ0PImPYu&Q10)I2T?0*r%8^ZsXQp|Any@TMUQe#HAvMpOk38tM z#i1UXTVBJjl_`?}B^i2hl6uaW#EP~oB<}8RAh5cCUaH);-lPn^zd%*iKi6dUlC-s* zinPr`u8qHg3`|55_{DZ2=|^cDME9|^zz;! zkeSR4qVg)f8Y-ST_#HUJsP?bS`zK7!?i5fV+;TICcdLwHjN3B14BiJUK_Gz;*XApe zogO*_%+|hz%%IW*U?rASCK7Q1DF#!4)xu?Fa@=PIjZfsT112|JD?JzVp*XEHr*y7q zh9Q%JFmAMBlo? zt$|*@py?(b=gFKtPg=f>ySvYf&!~NY1?1bLhL8%h(c_J#UPGwr6@`Qd10X}l?wBFo z2qYEX4}Chg(wrSlUTWnD!z4gv=BY5oC~|6&aj$aN{{ zpK>)*p%Qd2;~g=%U+#PT;L1G8uqgKo#*onisLGgiiA|l@CE1>Q4tBIV!|v%OHr!K) z24~kosbl79*Gp&l4qP2dXX%_9;vsopTohz+idkN)`1Gv-cqz+VA2Tj-1~T5Qch&C| zo8XT0BcHG_lF~iw8j9xX@2NOXI$Ac zGZ4)%$6fsiSZD%@lBp~z-uOu}8KqNj!azil=p!@rmsPG`Pl9+3&*<4Oe65sbLJ&Ry zoj9=TEbM1lAAFYi`1Pajesnz$y7|#}0Rra;yXvvWUM!wPVKIAK?7~V+v=fC<7{>k6|dw=b3{0{?P^BaEhCx83z z_^IFf`+wih{_G$6*+2Y;{=mQUv%lka|MXA(#xM1^{2O1t`8WN+pZg;}^Pm0a|LMQ} zwew5=@c;Q+f8!T_;TL}4ul>Sb`+I-)@BhEQ@=rMDyF5Rdd>1F=q1AVIJ7(xOZaPi!HuYLqhH z>(`mGLf&`^uROE6KZK=V#lOs?@uKds@(j3&A;zR11`IB+o2M&79tpV-B5&A*v{a%i zvI5L=BN`=)C%G~?OC~uBnW{Hc4ama^+s{hyByu|3YW0h4m#~nGdUIjwA4jEY)18VZ z#1e$fQo&N!DTm7HLi!uCUwPkh`kD%vy`Wx5EiI8vs4P3;4DK$^7rWtNekQvIBwxVE zM#d2{@5sbxOm-#1TqcO9yJm(0erov57N`POyMHfvy=6?sWhq`uI3qnl$a!=J`xnXW zaAbJ`PTfQ*USxL9q?yAD!laQVR2g;TkzIx+(N!_kMJm|p&KC;-S`Q}H60c;cvyiL5 zSR<7iAfqCB(JNuU*;ePhw?lCA!;KsChd8^`D+F-{T>{vXezqYg9Vd_&DAp1>CL#A! zi4w-tX-T(l^(Xx0Xc;uP3SF*nE|v+=1rUodARlSOD{jzThbmz&8*Ovgp5-Mz|v zVSHVsI};(KMk0cZ9ZR#0sP0lg`PA0!SZhf01bCDL$i{>;59cwMO*V4Q0hy6BbRi_G z54E7udEVJOh}rBj)jOM+)C--^%4(_bdOKT1yv7ldX#xHXA=+_;Ae%&tuq15i78EiQ zRH-|_Tm(p|d*94?aA{f$?quuZ_ zxEKl547g)%c;8w_7N4}z8SBY3^SI}b=k{mwVV{W~Ka=5y&I&XiK*;qB_K9zm0mw7^ z*-6eGmziWPUu9!;WKz-cVW-(dT14AiBKg7IYg9DKBA3HXtlh0-2$9~)F0(SBftNIB zRKsCyd|5Cx2Ul#LVG`u%lSYlba|PR-34k26Pt*S*Bv5%-5HPoLkU-|qp=AJJeTQ5aOkPL_ed9|yBNPCRAXOXCYp>_(=UUI}JL`D3qQn_ToshGBHd&3S=?pN#?0R5WPMb2tnbB8&FesVTFK|f0&FZu##lg5YIko z8oh7fz(}EodCGN-{t6aXvN%!+Mh-sZOa0;c6&NF+O;D$Be^8i_j5c-ECU%Mx~fP}D4=IzJQW8$~?ar{CKeeemu z6DmxIHiBH!gPKdPprlhp4CcBpd$&NQF0Ot!SHF<$zS9-1RG-mhLc(nvm2_E+VZl2k*{$xX7lWb;ppXfTAreq-Z@nI0_3%WM4 zT>-#H48+~`0|9sS$pAK4PvDtU?*!_<4cjhJt@BF8Cv#T%uv1Y6b$f2au;&bbC-8=i zi8e8njA5#OEQGK_g-zT|&30Rvn_EXD!yfwN2!zDI-kcV$La2;)o~-AQc3;Zx0;7K7 zkRecs5u)ys0Ih>0klNSfwbDp-ci3Gq+;qDCka1V(d#aib$Jl&n04F#l29j~LVh-e9 zb<+%|XMk1NHE{H%aucL|PB^=ZLgbS?)8hk)o;bj_Q!`JQRyQOk<>Z1w_n!CAB`noN zeU%Ld9iX-YI40fPmaOA0Bgya#Ii7$#mDSn47q3`jc#@)I7baOhFj$sRRr(xC3}?4J zg^SN@68Ku1+RB<|NxGPeknGv#xeKH(Jb<)fE*z4n zz`1I^6IXhd&6dg#Vhn{Dvn>K;*`7vKGWKlx8&JMxc+IU-RRV5-4YF=B^RPu5g_i+b z%q&PqlLi%*nLVe|6^+%=C8ON+QYB30Hteq4X6@zGOMrK?TK{1wzc$(z4+faYxYxf$ z2)P!b#I1Qi4EgSBe&bJEnH;`G4CS462dTD+<%NAO5b$ycDprBLJ7&}C4&W^a1C3GS z9UTK}!S-NQCmi#ye~n)~hsKYn-7KB`oS9_4PM`eAhm+8ZyHtoT(`jxr39XIykZ~vX z0bfa(dzb7k^8Cc(>IkgonWu~bx({OoyqKM@uP#y}+;Td3^00mUd~kC%j33(2551Uk zTKlcL!xkCq$#V7+Jk15Oo@L#8>cIE5d{}l*C4J5*GM6*5`vFv41)K0?`$Sdy!s#7$ z`AXcKna{5BW$vnES+RYY*5Qn zN<#K?x8Q*LNEh_t(DvrN8s{|F8e!@BR7z_P=N5Cw|Ls z{vE&br+?;W{=m=w{LlZ~&;5}<@CW~`-}+mBi(mQfM_<3^_y4Zn_4|I;fApXHsqcS{ zfBKLA#ozjyzxY@F;$Ql!f8{U#-Cz9M|LmXsNXgq=*||D@sYF*_hL)q1w4 z;6^g7xSvAzDcr!A zbHJ`M0d^&`3iAx-_&ibUut$=c2VPy(J#0vKMPx$I9ijziYhQ>Bi2dtL;PUN%gitW{ z=Q{R!)^9iJhRI~)9iAWTwZDdZdoX-9=`?)1K@oDNM@$A>7Um61!i_}iHo;(O=9wvi z>mHQ$+3Vs2Xgo>3te`BDST|d@k#ad*X%vDUPlF{FpyRUZ#T6!BWnA7wE#D~E znJKcUxM|w95W@ET-|xuVyXv9=683p=)*0lc#n(+$J~hDejlerEiKbyfwt`T}jhmCK zfQ5sW<0aX1URhn8?r|ci_)!z|{&LB`b&5=yKff|hPXxV$0W38-nQYiT&|QEX;_iG6 zUR1P*Yyv)sW_QDbdzr2@bIUd9-&ENd_MGSGlTD|+kP+(GE+aQTkHOu{^HBNBlpQmB zPG;841h68$sH-5Tf}V+aJSWeyOJI`4iWLZr>xF@VynbWX^s>CHIoi_Qc@v=td-i4| zkQuil zY_HOO0{|{5nXeb^bM8QuQ(aizQpaI%#XHEiY5~KPjCjYX#*?~>2R!>p+eOgKJc(x6x#BXpw-+jgFb$Da ztCx`!xXG;d$oiFu&^QxUCY`Yu2vFSC+Ac7fiU*q?a5SV?B8ZtJx2{uHSA0>0x!m`vw5vG2JT2v7WkZ1OD@-WQVLc*Cz;%**f zcF!|IsZWRb#>n#?-Pz%;aMYPuF0lLU+Qb#6BzT=y@xQ0iOVJA3hsuC_>vGI9pN}Cg zA-HZ3VR9 zlUzJKGrKU`IHEOyw)J*0nb|#G&$Erk-w2x7t4G}~mc3umYvl7elYz~9JwvG>NivOU zz`B-|0!tEQCa$(Y0rRS{KPy8`P{^*=UU&&r>c)UV+TLm?n-_Ey2=@vnNuE`?Ze%hq zv=*96pgz;1u*_KERoLfwc0ZY*b%5;#Bmqmz>X2kYE8nJV7jgu$?b#+_a{CD~VV>uF zKJx{tH-iEuh3xCWa&7wRWKN|}3P|#7uLIO-nG1lcrmL?>AVdc?Nv*N;CaL~<{X&f- zxr~=k!g9mtg&*E06iLbW_HX^X0w%N`>42rD78j49L-$U2GY!}#Zt##KJma=ccN1ur zOzFRQ;Qc<(bfRf9SnI8A7cN9_f7pC^4fhJ9OhFxgFGCkA>kH)OaapVaND zKt?z26#~RH50LXhA{p+2wlAO&mV6r-vBK!!geTeEJdf)+hh!$VapXRAqctLY%8wQ= z=j`*0Ff&7Co5~HSTCm>Od)tFR zY&v{@pX}PPZlT(5axE`lPWk#K)w3rpC5Fh%Gu6k0uy3OHgD6ShG{SO00J&b#_p16^ zC>frU=>jN7Z7!MYx4Z~u*mb_1-Pe0clEk%t6P~0}a}u|8rLrZg^Vxgp@w$XC89c9< z)bZ*q%8W|%eJR_WT0Tv%5jSMX1q6f;>x(3ry5GS>LDlC=m>#pl>py*EaKlr@iXx5d za{2O`gN3B|&91A-$Oqa+xa#O&D|5A8T?owGS>Df1Ac@`WjqS3&KHDkk zYW7mJ&Zm|2V!XFQX6~zd*NY3y%3olTO%khRL0}ff9lCI1F45_887OuS>u0V9xJYQeh#TGx{ zg!lTU@oSQ~zS+3G$L?LBLO?J~#5nbFfuf`rB#|j&pdkg{Zg}&yHjB|_$?dZdVo7JH z0Fs1!&Y78aFp=d(&s=c#R;V{rrbb(70E~#w`+l`%ra*3xm|Lom%PhFeRAd92xpe^6 zi>*jm4XII;b@#_%eua3P&^)V7yqsPt6QJ?yeE)f7yb+Z~hhs)aWM0!5i?lU2%9Mkmy`;+ou)=t|p*SMb%< ztF~OZkb*WziW9ITEksi5f#lM9v@Mlglb5O7IZ`*>g`zYMzB;qL!bYkD*q8*JH*_U) zX}j5He7{vQ3Bjt)+bc~ETNXnlmR#ln;I&N64-F)+Nb6wVq;&_b*p`<4tNEJR%N-G@ zwdM_pcf9M4OLto7&g6%Z zr9mNKQB*#i@xys4)-rS!Mp?mK!v-SGL~WJT+&ZN7vu2S7U4_am>J{MXD4AMZeH$ob zs|V2Cn~XF{!g|r}3a|HvzxDC*yp~ zgJ0}!Ft8y`a~3|blfp;Nrz_b@2)dqBG=W3ZT_C&h2(YZ#U5~r$aW`w$n4{Gzo^vs| z;V5DEtc!zP8hm{&dM2}c0V&=i@zaI zmbHPgE}+Jo)F2X)!W?R0QmNd{L+J#8P1DPjJPdir^EJPbl9tcE`qh8*i+}s?|MkD| z=l<+}^W9JU#BcrS-}d`{<`4Y4f8^)?$RGQoKl8Ic`%}OBx6LqL{2M?0oBsWu`s07{ zPyO-p;g^5;pZv|g{x|>PU-%2Z@R$DT|M_?R?yvpxfBE2#p83%vn9RC$AoZ)jI4iIW z?31t1#XT86jey&rpy#=aZ1Nm%o(ELsfrrm0^Bgy?L}7wf;dD8CBhn*+m zXrI;?eTI7~Yu~r(Ee@Y&KJFwBp?Schf`tK0ZSqNqyy$RKYtba@dAcZ}CPw{J_-1!i z5bW;EeV`Wr@B0h(R5JY{3|85j1ewffI1NBuG(f_kd0L=0o9$MN8`XT#qY}vD5W%Ia z>}nJ1ptJYN-qyRT0B>;M_GB_MSx*h76aARe>ot1Cl3|uQH!A;3xz0qhFi3B1Yiw!( zOEP*ABtUv-WIeT?y8X1c)#O0q3qktwvGoSb=EK^tBy2Lz?q@Q~wWkUpNtR*GT@^lB zcE{VEsFDkn378L|J3!u%n#-CQ-j-kGQ2ka^YBS|XAG+nm4>#=3|VUB>$VU`8GIPo zd9rC_%6c-Da~??eu*q~ar#uT|u(j*U)rm0Av}v^~RQ{VulHK+~$8VZTT2N~yxAI-6 zi&5*8woPH7LG*e%3rMU~QfN(KAIU@?6p1iYP;^L1#Fc ze0e@o=>ZcqTY*!y0IuGOxsndrP%;7|ng^QTKd5@3^V-oSL6%Jh2|2q-x?lc;%gYpE zA@wQ)XSYvti4&HxL`wKF3A4m~J*G(-#tc9sSJkxyEWm(}WO-%*9NAcIajZ+7Kub9= ztQ72-P!o;>NeUfs z$@7HW-AQ7Kx19}{I`~3>%Au0$?@U(@OoCge<2r1I&N)6q+goaK`BF)O(I?OD+IiQR z9;dZ<5GNrq2i2L>U--K359=J$db_LmS7Jp128*0=;YDO(6vAfD?oVGlP*1` zTkXZE?*w*pqufGm;XZsiEk@Waw;U%qIM3WTI%J2@4K z5!cBqCuB)7W1U{3-X~#;ZV=q$8E4%p#)*6TA<40D5|U*KUSp3G%3X2{>dfqZ-sJAt zH1W-Z)yyoL$#t_u3IKi5oou26Y(9T>!&k)yWE!V}o7DM8hTtA;55Vj6oFx7L@pM&x zov-yv1*#vyFkwk7U(Oq~g6PFM1->@2YO@Il^UT?0A@iy41ZaI^LTJtgH^o~*6OrAM zVYYkQCuCX7IcJ{d9A~CpwWHp?u|Z$pSrMttUTy*|bxN44$>2KQ(sOY$Pa2i4NF=aq znCH1QMpcuUoK3QAN?UB#=tZZ&P7TyAy;D)>)uZOVV~~IwiZFw_Jf;7RKk?Mw#FSE) zv8{u0F_SA0z@3!nAEyN|=95DhvK~r4pW!*5O_CaWLi3sFW^BH=jwaHd?>{>!-1d+e zW|i!2%l6f4A2w(!BtzZ!NjMNcKxe)L2+xmkgM=(jAeZpi z-4x-4EpRm?G&Cp8gF3|4L7f;1oMd*tPqN0l+u;Zq_HeB) z&AF~0D+((%k|?Q~q+gn+A7tQL90#3n^)j>P+^ls<<~DI}jANC}Ohi~|#@N2cUh95# znJYOhywQP87c-s1%uJ!qm2!y!66JFjUG3+Y`MALhSM2!laP6zg_hIUVl2rKjc}=cC zhcsw~S8i8C!6jrgC(BHlX<;Pe;npUgHBOgFb`>%`N)wo*VlogoPM^c7GfJm}twT6) zV||sj`+e?N_xB&+bA@p;cXD(3L!l>y~Uc$miUs1$AapGfVn+Jxe-IdC21{ zw**el?o^u&&`>!m>&_(eY*9q;(|pDxlU(7ber9W{OajPEWyMnD$2(D?(i1!TDR5h~ zS0bkQKXqf000@{2<>1xtw{h>i$w2+hWC|fAlVJb|$^db7?wIK;HDofCp@9DXjIQKW z1^^{skW*VK^y~B9UtYCH;#g0t$WY@}SX5Qh`CJmrY=thz>>%I!q}oU= zZ|*{DNW^ck@=_EGO~c<2D(317=+y=Ng_~~LtaMA?6hJUmIK4U4F4m;`y;2*jb{7I# z%cz$`dViJ;3D}NmFNDji$o58&`(%JjytBUw*loy-nBfj|Sx&%MFVOKzk5n%$7J!Oa zQV3_#*m>od5oxhn!^y?lYULjVJCAtVl#_KsBpJd0#kSB}RwoT%%j?dBZFmN&aj!Ef zW^;8Cuuf2`#=@j>tF~L@yasYv6)MCl^DFq3I#Z$ZB6pzW1<(aRN6}y!s4MP7-TS}_ z4cB8-19E>lWre3SKfCM(Xaa5}Tj-6jyH#i5QHFq^xkbeKx^UMU?Y;a*O!v@~ez21T zy1{&x;L7Bx1D$z;dXOpo0{hxksZGm7ij5yV`6s{fPyh6P@n8P(Fa6R_eD|X~UlRQ! zbB_{gY^m%nN?&3tTuo*@cVqS0Ok9H{U;z9SFm{})W_Z<}esg7j$Vk**|Nir5|Lgzi zPyd-e^SgiV@Bh_b`yR&VQj`7cw>yaG43kH{7v6x<$@J{El`E1^rNs-*(aYi0K9LO* zVO@6et#RP)_C&Hh=a%Z8k3Qq@dV0g6E@T+A!O70`yVYeW(d0 zH~s<~em%66J+%l7*QBz3s^Qd=G|nf2LkKddd(tHgL}-^K`1+C0cgr07XaDF|{?b4E$A96!`=9yl>o@<_-|~BY-|zXkKlVp|{*V3qAO1uC z_V4`NKlS9h4L|ie|IOe2JO0Ff_#gj?U;o$qgTMF7fAuf_)&J$s|4;w>|N6iE@;~^$ zKfnI{JkNLYJo1%0cZOuL+68En8&EUPuFu$=pbE2HWB>~DJmHpvwXI23 zZ(N@M$n$)iB{5F}Ld{2tdoEi0>+U}J>J<&(tA}W1@#u1 z6fwypL-(>5w3+ejOqwU8i0N?a%rsnH82w=XCV{Bje7MepYkl`3az{h%PxG~DxphZC zZXJ@rx)is?dlD{}GV@Ic_uiz!{5Das8tg+0xbm5K&bfO~lB}me69R6ZA-McSzGYuS*V1!~z{^%2~*b>4+62kp2}AuD&Q_ zgPWqA5*}0GnsW1lfaFS#Y1QXDZhuPFwi+6nb+|LnXTP5f)RTrp3mZ#nx$bz=9Pg&G zg;SkWtg(Qlcdga{W!kl@0B8{r-TnzGd&^GLejY;5TN|hqQSx+(%6Zr0nR#Gof;4IK zZo=N%L-Q*IJHkv}Vkn)VVC5SlKgLp(dOt1_r=Ru0Z-zxlcx^$U$S`f^Yi7K?mP#^KNLq0IPS>X|GV2#!X|I;{xbHxJ))TE0N9*CT?djHek)naWmsd z&C$Ik;FeS~Lif}b`^EFM=sJY*n7Y&Iv5v^PBCYqZ_$_Ojb_q$UO;C7AoXHN zTn(gI-?y)aZ2E6i44EM=d1k&X{j}y8kUCiEV7OAaqiS6e)Hbi9zKyerJxQ{!STq&f zq|L`xCA0+9w^5?y26thaZoPDs<6cXI`2v!PS0}Fb-U#{XVNua72SuQDc394!03JHDO zOY3r?V-BKQwoT?up*rP_9zaY;+Ep-g1x;1o8H$~PezD2P#Zis>Zf&e-0T7znFd3k3 zqdYX*yN{8QN=9x~TNNg=daKEx`-=HKD5{4M`gKt+aFt-fD!FopNRB=;spLCrf>&wR z9uhS`f2k{wR5JlC3@>pNQ%eO&C2(1j#I+g#%>uW3*=sv;f0Wc)I_**iU_A*q=WGK} zxFSlY>7)r*ey4uk>Tcl3a;q_U4KFk?qVX%4gXy%zN2YPD%B^rJg;yBd-xJmf?Upbl z?49Xe=(nq}R|NN(C=_O&PPF_Gk5si=Xl~HSv>Dnsh4nEJYwUMkDX_s1^*TmK(##^$ zf~<~i)ygzU6+Ick?iSCN{;MBAk|gl4Yk)%Ln~6a$v@oEOj5KEEz2@LEi45P+af#0@ z@V{zDg5NY+nVbHo9dn*%U4l(WUZ(mJc$c8;Wn3YdX!1jQgGpahb}u09V`R!fQVK}5#jvWB@`6#>aCAI?2J7~Ad!eTg``^Mu#(%5TCI1&}6X zoh$0x+yG=-%QXK1DvGH{EgG)e-ul0nt$eGAlVS>TdmvmLhOz-2Gby_raEEvY zR4%{@g&%IhyFEaX>9XDQF>$^P0ZqJfJDlmwxhmr&=3XCA>r=fp(rLYO*W(Hsa`hE> ztE41V*BJ#y1j$W1_bmS(Rqys|-Ii7N{eFG!?^G345HOUmg2PKJ1x%`txh0O2r*afz zljpo7@BY&wB~l*aOC%d?f{Q@xB$krMU~DM?0}cl2UT4kT^3cZI-^r@__St)_ImhU| z_0})rGRIQo-WT1Yh*~Sm^>z;6L?kf!y6@>1IkC#=ev`uus4Wk>lpNeT*0PE71XA_h zPph#KM(&1XwonWLle0;yjFuAo>E2>=WPLolAU?koJWUSN9hDJvF0;Kk9nfW6e3lze zfcQ*v%4VH@A&BxG%B$SDR!?e%Ac>pp1kD_;0ch%+nyfb-{pg?6OiP-=aqs&%QXV+e z?|&b{JSbI3f=Swe$qAdu>V(YI03K)w(Oa$*R0L7w0MW~Zt|*qoCp zcJJ=GYR5;QHrfa<6(TdU?`#N!DRkhwHGu)3mTsMW0*{Xas9~OB-i~bRIP%ov%BA;tYEU zEe4bRm9oXkhNt(rcR9o7(y4Oj?)dJMk8rgBXBlA0o6a$Jq}~X1!oVT(1Qcz(T7$L0 z#{_V;NP(E#N2Hkz&pZ<)p?Hwa$H;%*JL-5OKoejWudezRpYovcmOF#3yOs~YQtSy{Gn7`);PZ$<`$`y(4<~?gxvwKJMkcALQKs?S zCCr@KxZT#yLNLV5HanT(QrmhmCLBvtjK#^uv4PCF$O$!}hJwT**~}%`uC&IXl=FR< zU%mm|LgMp(D$OC-c{m_-k6anV z9EtOAmiHy?2gyb}b(1*-kDvJtVq^k;5F{!%_hnzp@JE4xfzCe zN~_G&UQ3`cGFxs*DIYay!O;#Wf>Y%wg?TEXXb;Dm1ae0$(Iwo}k{;oKvwyI&D8;n0 z1II2DO_fk=t=O`6GON?67D%r$&~Y2nJTRYn1>1AOJ~3K~w|NZL%G5LKlI`=gpb81nB!ZVGpx| z^SpojL+Fp?ax1un(nZ1>bMNXj3>}P0`>ZqBwc{2o**9F%d{eDGw-pv6Gx>#_MUeyI^nI5X5m{1g!HuOk7yp4d=TN>gX1 zAW6??pfYYCWJb>j|Lw>fLOcXPZGs7;`%5Uf&O8e(Pnrc)WzRgak6bevH(LeqDkuj- zZ7$q|0|Mb`FL~Al%t^epZ<2s#mmHETrivvbK=1+W%>FzYx*Ix+!7j~4($5oM`khZJ z@m@lL*%+LpOrN>$V8k`93)dT8|Bc`LfB((D{qZ0FiQ{UiTwKW6(U}IbJV5{s(-TG- z>rB$|Y6v^YE3pATge8%a+uDnzg}?>fP=7{p>=n3S_w~nr@+ZIXtH1VN{@4H2x9i~d z$ERCJ@M`?X;ic$q`#dUVKIoAo4lhi;_Xy{@-AFPub)4Hk(B-fS3? zGfn1ZC-T;uwyVC9TzuG&F5}y^i+k^vl8wzXA!4$v+sc>CWm2P_odM*2?Pf?e$xUu{ z$!0^AU6%Oi;GIme%_a#O6S=qJY7lc+Ww=S7%Y_J8$o+jJNx!y}WSfEO1w>;=UiB{! zlu|w%Pr6NDVYVaKS9)O*$77cC1Vw;|C;1BLIN>-91Yp>R7P})3bw@pZ^>)oCbM3Vm zKK;`_`<*}elmGL#{>guT`K2HJk-zklKl#%?^H+Z6XTS1OKmF5R{)=B0J`DffPk#9) z{?ga}=6~_mzx|DG{oe2XtKa&q-~M0z$v^$wfBrAN@yCBU$>%@0uHj1b);EXv#_@tp z^8U;w9Ww30GA5WHGc*V}3Y%?6$+qTGlk9a}Y~$R`FCz$|pK`3j^{WrK7;Gtv^p4ej~WE-ZrFDJ_hDoJj}f!5d2At5%dWV?EF*n^Oq z*$tPV&0@`xm0@^_mew}?-P*E^5Ic^V*4594&e2Jyif~0>7uXAJ1>d#qM%+=!l_E zqzRC*awNxhJvIAh@@dV^5beBxCc#0;Ey$H{_zCpeQno7x>33awQgGwL0O#B5i$`5}bbYc84&-8Oh|zo%BmaAO{&O z8f!U9lB-;<89U45?B0kaQ`j1GZ1O!;H`!hSA@tWOLIFOYQ-L2@igQTfT)Q{vuC)VI z`h$dqs<8J=3*$ENUTEiiWn_*sGVLi(8J#-YkquNW@h3Iq_wTV{x0c> z&=)v}x2}r& z9SNIAa=dVaQ%kSGc>Z09UHGs36?Zp;XI-1&S{ttd)?bhFb&w?nM`&@AHDq_*ph5iw zl{L&5a8AYDSB3KeLALzpRMpDTouOa}s#O1;~^6 zK004ua^pqHR@aq~?v*DAu-NWh0)!RZQSM1OU@hFy64ABBxHAB#-)ie#&-)#vrTRAN{ z&-X*6>^hshyYzToQNm4Zv*?&Kp%2>_uDtsrFzEQI0|Y$NE=FLJwdXvw6qn>85wm%4 z&SuIT(47Lxai{VIxi;Vi^h)lB&x1qOzYn@aPd(+1&0_fDIIG4<+ z{&7sg29JBesd18!-XEvIcbRLP&7OfEI$p;OS4m>_zPEAsGl_&5ZGFyjNGve&u`LN5 zM-;S8daRXaNPe?44d=KwWWo*J%?nP9_k^JAYqQr|&z81K&;S!~ItFPB!J6^JIAJ>~ zB7vSt5^WM{sxjMDm4Ue?0c`QsTC(#;@xmB77#)X1VJB1Umyv z3u~&3B2w6|aO(+vQ(ItU8*`o^%gpTF&&2*|ew&xV$YePtWv8y%Zd#F9i`Lu#DI)GJ zmo(p%xM(u40(O?X0a`NAF(gh}E8KQL@#4_y8edstw}sUya6!oHzAu;)0aeM6yuk?z z8-|y{p_oEabAT;ofXQv`ONSGn*}bf*9B0BAaXaF`grPOub44l#@=7eNI(A65f&+VM zl}-?U3_6tHTuwF_k-S5V&wy)jLq}dDnXnc`Sy#=5G*Jh}xfzq=n?Ts9(zDwlH=X}^ z$%u>E)g?};UVdFBN#pq|%}g_!;SO_$*6`5;WfnEN0Yfg9urtY2{)-{pe5!=Eo18V4 z#y4-^-Yc7)SYHRC?G-RKgbicTv|2<$God3(qa85pE?dAC@t9{hXeU6cDY!aIm#Drw zPjajf$Xg!4fmkb6^R@&163nWWr0tD_L!DGL9}ue7n;* zx|a+g^o*_@wUf=cvV??qS#u=EZhN*Lj8?6Ea2LqN^LU@v;yF$k_+V6dUR&`K;3X7aci;+Xf}#+iXbxf^>3nDcgS*KPAu07RdW`J} zqa?JkRR53ObKaqY5<^{2P%$2c>~{ClJOD<(5N5=7XV{97?3^=pj$q2PUL0u}PVH0u zfOc47RP#ohccdmT4i7bOvp$4`=@mQPlbY@OE0m`qLdE#IDY_~6!Ld-Y>91qpFq3&tVESF z?_wT3#ZLDNhL5kANp^ZQTcyFl-EHfIOT{Cn2j{&U53lI^Ij~P5POvb3R0PNoyCNqF z9{I~251p|3YBr*T&R?9@9P5Wov}KQQNDrj0B4XV~MV|Jba$#u@f)h86CzdUtoyW{b z3}qcBG8}yh`_Y3oHnyUwM^Hl>)iJw-u6|X@BL3)QOIU?d0G;t;TY-SrMP~$YnG1q)>~*{yyzl+;mw)stU;Wwp z-ar55H~;+4{;bYXw1M3lPBKpFpn|)%P6JvXY`e&AVM&uwLWpET|4PQEbj1WYhTFZr zfA1H6_(%WxFaM2CZ$7<~Fjb`W+`!#|?eW7n$lII0`9J;UKmVQI`Q*bFoVSt)M~5i( zW*tOFy|{*9I+&WyZ|dlXdX-*-&fr-8ViIN!8lDvsB8;>FJHU7kDQhL+xIhwu7SygXv_)6WnT< z&dNzi%(VS%Nku=ZVW_~fiK8Q@`3O~Qt_yAcIq!DyOhMqR!IT*Er#W>+;Vn=-?qc5Y z?=Q*X>5BxhL9y=rl&?p9I8@no4tP$6oD0n!xJLQN+cI-Xox^9CnJ&9_cD)4dy$L$W zKIyHqX77R-c$>L5$9X6t<>*b^aq?Vn=nWd3-N1Dnf>*)dR4+$3wCr6q(NHH{=>;V~?<~PMRE^cP|CBw<-vSB& z9O&@jbhL-|l>{5#Irb3W-=Qr~=1p&?3YQ(lF+7WcBh>;*v20q}aSGb^#&lGZChi@j zb~zPKvNq6w^7-BVe*b*-TGT&r@ju(xRm#D{iDe z8a66^)rtz+`l^lV@hobXb4#te4IXO8e+W(sZ3##l6zpy=j_T7EWenC|rf!(Ls|&zF zweHb53-icQcfr|5ua1HRIJ26NOqp~H37!OmTECv5@qf&2!_F{B*Gvu*9>uP?II{Z2UTyzt{dG9u$OlMrxn@AfHA05J-fhLlb7v3oaD1nNfxE%&BTRM1f#oV93p zFh0#wHim?zOr~7ug;VXz8+`^e0#97sCg=tmb*9r31Mdt0xE{hIb<@godEJ{CeV=Sr z8u&}+3JXAzd7FervlBkwuQ6!{8&1KMadL0-MQ@1Jv|4zKh#Y%aDz#x5$Xrv}+h?ch zS{6`}9AI}6Z4l~%2jW?@JX(#jOKbmjk=?X#J@62=_o>z~r&pq9K4Ff`Lgx)6>tH0A z>k9D9Z$RaXIx`6*@s>-A=fGys`gAWVz9y?KH_nV4{H@1@R!t}1^YEnOx2vWa9jozS z>qs&-D#$^dkq~FlCfcO=La?u7A+KynV0kS6!c)YXh0^>87rJ^7CLECTjZ~N7A zI>4M0YsM3K(9WM!XyEV!$@rH=L_`G)8E679D1~ ze=)#T9_cM=sy5g-Q;|5YJUvQBUbO_+n^CW~J_93DK#ut8xy%}ZCc81_Iy2KgCN4kN|>%NPJPd(9(>c7=R4dqJcwN&3LBrP2)aX)q_BT0@pp?Usc z2%-L{=QjIxDqpVPTrq_}Dm{eDDPPkboJnS!nN8laXH^cIcaHGq-)O%FqA;f%7GxR{ zy3!qv$8{@z5^Hv9ohffOd3J4;6TsB~Sj9M?r6cMXHs#eB1)z3#t4LIgFb>HHi$o@~ z&?=C+Ox{T!ns5VHc~8`R>$?YNoFlL5!c6fMaZkUXI9+vaZ}0uS4?In+4Wg|gQmZ$r z1i&fKGRY0;qpKOI8KuTfGk1{dmGudWGqbxhpm*vBkJ}PGAxM12eI2g@C*E4AXVPtv zHe|JdqsT{R1oo*E2)g8nKUllBXi^q0Tf;jDGuEJ`r5++#95z9eJ%!w*QR)GJ@9?4> zkhR$hpiF&#yEGoMG*hP$BRgX^SfsseZ0jeI^t#g`lrYrcFE5sQz9JJ+G6v6DO#5X; zEo~R!#PiibPx#NBdgU4C)b7Po28w5%0U!W&d+rQ}Czes~hCtX>(Mvcqo(f!L&wSq| zod*jvbtr?ICtM_@nf>^%BRS?1QIm{=#+Wp=6^u&O8++@FB-U1Eog!*8Yz5rbb=jGj zQ|E&w%*=j#emw*sRUui>C91tXeOrN$U|sHWpZnzcp&$IwZ-4t+!!!w)_OTfnUux&m z9As>?wKZa)(1>$1%GSo5TOeECSQ*k45djj~8WU0}w3C46F?RpQzNYBenmtk#!E3 zt6oPSLDR4poMWa!w|md!P+A`9oy%4Di=eL^Vd zE72Llh;v}=4LXU|QnVkEwZRS#4B?TlB0jeR4PKIs;{hb-u#A1ML5~P+pQ%(2 zfami9Rc4X{FNU*LL`$b<#FUM7^77`vAG((_G9op71yjOvKYjLy<_1Y!#x8f3veoD7i5#-DL2&T|vmEHz1x zA_?d~l=P%p^*AdS8wsn3Iae;@l~!h#$AQ~E=XPr*K$FZfs?lk5R+1-@m|7?HOVFTmOY#Q!Asn2u1?eWsH3P`>!wk$Y1!{FaM2C;kAJY*-VbiYMT-!Il{bs z@OOXrU;eZI?Vo+&^IvTCCy*zC#G=+&5SCLyq*d8mNP^NKg&=n89FpDKCfQ9EvK!p& z+Qf;Z8GOT@-@!gDIwa=KcfR|bzx-4G!B>Cb7ryi9Yq=7uT@FlxRV>7Y@N4#S*W0iC zgTMEufBdgMxfs}FsHmh`LR}~);-^Lz3sm2`hTw*|KFDI~tJjP(kx?zfN!cqj*2HDs z-adTs2Y$f0{_qd}^>@GXJ!O)6mwTm)<(V=eo4t3JtjX?@+pzmG6-vlR;1KCN!Xn@SA2oN2 zzrbSXP}`6jNrRg2*k>B~@4LohfQ1f!!$!B>G63raTet0u=TQJ)ok*6AXBf4YQ?WIU zj>>iU$=ip|fB5jhxxe?_KmLDy|DXQWZ~u>f`|thT|Kaca&tL!TKl+1zd(G#*^g}=L zh0j6w(w9E>@BihW{My(4(_j9TU-|j}Fr$gO5FE$G zSRZ~)@fnnXF{l$vlIfG^))xH%=1fMtMO|qD0UUl^dU+=Ilau&vxW>ueZ!^V_hDROj zCar{-w@8}6o$ReeynfL^hQQ1mtM=($7|XBDPFj&dP|`ic%5#4X3P3Lb62`I=A=~sg zd6*Rz+s%arba9QA8DXZCAC9NqI1-mwt@J9vGt4Lx=LDUOZEB~AVuIZ(;PfoqN?2yY zRLCSaBV?v?C8?B0;Z#vmAOH~esCfW&NRB*XiocA|Z=N;@n=xj%z-4Fbbv1)+`WXV9 zwRxPYq7F0cS|JgO+ZhLrCJ=oRbP~H29;?XX;bad^BC`+~3Wp{s`wPUo)@XOC1T_6< zU>VPF7=jychMZcPXa1rLaI%DqAHB6Q?L#Yj0x!tuMp?8PR|l|liwJEV4#I6$Nk0xq z$Tg-ImoBwf2$&9Qmw^M8aHn7NSHkH4laeIU*rr7~G zJ-h3mZ^6J&=IZmH<2!S!V8DtJHo|=e6Cvj+;IiSTLtj^TFqZ&vI@1YEYYi9SQ z$TOTQ8fm=Z06qZGmttZ(U;!owf)GN0I~x6#6X=3ZpX=GnCYjN3<)sIV5Jv4aZjM& zx4U;aXf=vp9GP%-m-af6x-VqrYLIp&7EmBFY|UMZQ}1XzEZI$&>a^bz#*!1U$LXQ@7uP@pBz@koe^fJk7c8kT?;mB+AsOwMmoV3GceS8? zhp`3(X519P@(493xfgIstmL|6q5fR+BJchJ$q*a0G35-L#M3S;9w6L@cJD3OAJ4%M zeKSCVIk4&UXx}U+3ox4cugH_Hg${x}UG*7vsMt$nW=@P^EJ|$_NTidF%$RKbLo&|I zc?h%|6=|IrYom6ghUlpl=k40}y6)M;KQo^2dz5xeNSN!YI(0lXNt2MEDs1uUB{#xM zNOS2HlL^_zVp+-VSU!jcw=nABt#eS#X(x0CAE|`>>G9CoSisI`L7%xCWRLAzAqXJ` zv&FhCt{xQvBa__CQ)3}j9M853@~;1>`i5oLi1Q}od>@*UlHj?aKHGSbDx*BCb2ktg zaRan^{a8Oo&U3?I=tzyK<)OsTr#l2g=5%DJjZ#WWLg20bl5sbjgrk%R_u<63VT(}* zuAI(QFeBSInQ&ayi!K}75SXDcreUe=cP+r-v5&Bwnn1~T?5-H{pe4rvp3e^7wDI!& zf}GE*1Z*cWYY*se9<#zEX9=HPK<&{#G!PWK=@S2IcPX#|zSug1pn>brQz1VyebG$dQy_Zd&-UdTAvoolkoIOqBRN#wct zqhbs&1G+84P~T>ft^6}{4NXt)U$)hF_0~Ff#$KNjRr=jHZs2&10h8;fob_QW9S^$6TnXEP zRsX_Cu%?aR3uIU19D6sL(8N`C+yaS{$~eWNEB z$qWev(WkDiRnmej4{mMM+&eP`cj7UmY`;1)r5Aae*LB9t(#JMBBLjEVr;fS^u*B(n zLJ8J$0vU>}z}j~%kgH-8k{vw@yu%C~5+BzX3K5VUl#kmx1m+rfDdBqDlz?J(E_S86 z($7#E(?b*x21ru@boam*GFW@mZBR~G!n?1r9LvCXB`5Nd?e~xzN|EiYt&_`GDfrU% zYc1;ThVs00hBPnI@~`vl37mQr2+(O^ql|sHF=X4(5wob1k=@%NPVkz#gPlJF<3d*m zl4NjmcmkOd84Dcp5LE0G6Nk@0_B|LfHAKa*f8M~%6;{J}{I9Q8_30c-74D7b?!H~u zCVIW+FP(nH)i%7-GO%V`&+;RKo#+@L?*aVae=L-R@+3J`QV$5-&B_#DVJjP`$9J&g zDoRO6Od2;*+x-9lAOJ~3K~(($Gcl1(#^^D$GRq5LTL<mO2`XgH*R1tplyP_( zbS6mpbVx63&#f~|eSp4Qa1zemS>YL#0W=%qQDB_SZA(&X(vu8Gcupf9Yt%xKLIpI6 z`M~E)c6X~wr7IBIP22$>2IXJ9Ky6$jeUse7oItH^0K zh1nqi%A;`X4%AV~Ih~w$ks%5F{(`*U)d*G7m@cpgaVV|T-Mdw5!nFM?J3iV12Mz?r zDaS%SF_t%NY9zHVpi+QQGILd%3e<=Y|elXqh^_5@0JLFo1`U1xgC-u zTgOZ?dMA6oE>6bHW_PReeV-$XL+bYcu)7BFe6wi+BzMlUq-4{wFR6;h9|1wwnlw(A7Q&`?1g)mYLfpca~qJ)w+A-geb z7VbbQ+(f2=HxUwrf!!tOtn+)Ek_KYuL zwo^*QCoPeIWa)e+VUFCuhO1p}qNoj2l)x^za=%)I5tO~xcyZm_GuIsOwW(od@141m z<-T*jxUNl-m+4lmgKtPU#mkjdc9;IVUjz~~ue%5B-uL&NA)DNJCG6}e zWp-b1qCpmN#$g@Ilicpf0KsMEoQQk(K|i9v$oXc+a-5tx&(am|yL%5oP7n;_PBuEq zy*+s6oSRHYoJ()L*p1}wHk5-xM)z8dOW2To1L(E)?d5GGd7l!Ha2sLneP*c?*XMwq zJ3BpBaX!iQ;nNQbeD_cO{I~w$KmPi!{^L)+^!XqEiNE;OzxtJ5`lVm`4}az>KmOxC znzwx6hd%l0&wu3?{^PHF?>m3%cmJ>7`$xa_55NBF|L9--i{Jb1H@@|`w@t7cW}{@Vme9zP8h4%kmi?YNAhXyouELRD|^>d&pyA750@51ASQ`X?HEp|H&F@! zHf#h4!VPt|D@)$@ch#&}Ge|2adaF3*4i)&cQ_!yazGo&II}q4Y;hgH?;Y$#kfdTIO z9%n<&a9wP1cW!CiErS7d6NFYe4|GpBXI*(RQZ4)Z=8>VPysb6UQKt)*3C-Y=wgiNT zY1uX;OXn2sd%V^LNRm35dlqFk4SfKpq;Z(?Ega>JxdUOQ$fq3xwVmW6cI~e129Oea zuose|-T;wgIz>X~o7#1fw4QG_Mf0f1X-2Qj`;)TKiVGYt&)-gVo#yc~FOWn>kA4E? za_*#ZpKvJ3;MQ^`>@^0LsM&auME8kX0)*nY2T_F~WHZ;?o1V+(>68BkwPniOTShyq)bBQ^n1iD zX(~i2F=Y20>zQlstr5df;OK@eUpsUXPT@&|NbT~mkVvvsA)qNgrC}}BO-_%oa)3QDpa$G7=* zTpX!c^1PkIG0AvlgXXdH5m&N_C&7?F?xG9r_sT$f+rp7O1DFIh&SSfMk31H!2ZYuF zawLg`_@F7IxgY>z!ukEVBo(K@xLnWfA@Oc5apX*}3;^uCZv&}{q@$-=At!*hr$~}! zh=dX%wf=)va%`K#A-Os98HZ#ayKELaUZa$YUOC>9jdNYumKjjWR$T(_-t+dp^ul$$ z?QZmbt=E;@Cp{eBG2LWVUpwNNP2~jk-np8*GPVqUdIOFD2-sorq!P_F*-)4dGC&9M zoaX%)c()L3ja{7YIJ`&Q-Gu>jVUAzaTMeCI0GW_ebW_fs z#`fOl*Q8@)749sWJs)WzX7;{Ip=u3hj;I76ITrg2d!-d-C&7>BCg;eI&ZvpUJ;uVl z90zew$w94VQEU_mT2|R{HpmmlD5{Xn<*4`F)clzrc5&hSD>zAz=}CbjH|g~nLvk3n z84*YCPA;R+sqO5o+Bx1tGV**sHKkv+;@TcBxHE=z8<*5^?j+5d9cOs-Ox3V$Cg7=) zy_0iTog{8nP!?>{qcPd>4ZPEVJSvcE$oqMR*)VcyKZImv$%KwBXR>F7Za1xykF`kU z4YA9Cu;g+i&+xo?1W3l-Ds!<-WH*6YbwX7|*wB+QIY9UGF<3fK|1tuGHnb*9fT$L=-9yo+W%xNOKHB?F;wE>4*yxnG~|B@`ar~WT; zeTKUBPR(5wVR($v{@SMk{e%iLk&vTh)WFnZvNi6J3jm+TcC8(9^go}{-igmNMA}&g zSuGf=d2f(BSX9;qu>yAZHIk?Q1KWF-tt5Qj3j2t9{*xnU6LHdRBHAwWqbvZPS!jy^ z^oL!f%tEpUnWJ%_@zyJe_AQ?wN~T5E)k#rJlCA$1g58~KcFAtVRMmT5GxJJFH&&Xb zcA7cU@*I1o^SUzOCcL~S^UQN2XWmcHrhanlk%LG%1pPBD_vWa1uD2?$Yo?!=j?+zY zs=G(ccXFKPtZI%%up~?RP;#SdPc{W}9(rNQ#ei&*9;u6Y@)~~hZriND!?JSY$iV+k zkzG3r54=+-#EjE>PcsQla;w)&K34B*NVu-q&Bb(kfwG+nbHY;dKi!^NJsh864S*O8 zA)KJKoyX1;T&O|%duv1Acdzz+>6AOCr>F? zc_>ZyzGr5-#6wupU=e1nyM={iT^25)9`;n=72VuGFyzg4T2MBGet!&rfHS$D`1d}3 z-OM$wR_1+QA$^ka&UW^xxokWK>u);3A!$hIe8P|oWPua>PCfljKW=L(g0PJ+r`h0<<3Do$+vDn8lUW`lxBhP30uPo!z~z15@Yr zNyfJ%R&n4-9(KzrJIw(|pVa3(xQEkyCkA4i^JekdtmA#}T<5D@5KgL+ct``BcvXt_ zx&!YxyPNmSe!?PbAlF>GJ5sNn=E$wqawIS3C&~F_1L!|Voo9&2k#;2Jj!r1kpg zrQ(G~*jk}-c7G^z`IkVgqV$8x0(8k6T0MNGyK;s&5e?;N>RhwC(#f(w8fi6IPYK=I zN^{L@(uqn9U1&!dm=GF{JbNA;U+Ra|^6)7q(6e|+!gT9!!%TWlK*?5ssyzs9;UEW) zsgBYm2Bngh)sv2H^mCt^d+dr&VkBhfM`AUryqu-p0AzIcgd&WoT7`w(To%9-3grm! zn5ut)Ubp*Jh$iy7Onhw0E2F|?HYL;L~ zzr+iXGtmlh40}d{y?2n9o&~1&dqK?*>WjnG6%a!LXFj55h9gj=ABnmyP~2>Hkv&`X z-M{nPDeahnA?+PHY>P&K^LEfap$J%#-91jU_YGvFfwavE$Sz*VYEHG*ZN<}&__`yE z!{&C}Byvj8rL*XEz5ynC!(gH{$H+1tKkmQr6aV_(|F8c3ul~xfe);7eo40F#%Q*%8 z)y{J?QxgbJkUrD|=!2Ykaq40T()di0=ik|rVH2D({k+|@7GPF(_nY7U1ApU}fBC0= z?yvsIpMF}DfjCF8VB*F`c%0sXoV-^FbSz*s;NCdl#m+S!x+C2>i55_juw?2M%x>a~ zeH%8{B(KS(ZeI2@>Hs=4X7vE^jl7mqSp~0jFq{gkkeRV3)gCN0 zI8K)ulRJ0LAv(25cMT!?!9hNJ@Eg|~@bPv3!T973P|IN35>_`97zx|WH z@bCWOU;FF7_!EEWFWke2uk)9G`X_$o=YHZp|1baEZ~x!F{SSZhAARS4{LcURPyX4L zUwrwsdHeb`S3~CsnKOv3oL+j}nBAE&@{@QFB1B9E&Pk_$ys?{r^gFL|zEv()V&?9= zN(O@)PA*19vwag=%DBqJ&5mJgjIl-cBnqi*$+aN64K|Noe3id-tHo0b&7@W@urN%rmY&Lsc7j?4ay($1|1pp=h_tmLd ziHmrVbS{+#cTA+jsG))xMdF5Ql64m^OfYAXkk{_&jAof@7SmH`yLI3iXU{3D>H?67 zNw+2tNOD2VKgc4_>79GGGrK^L);3KEMLE}Y9>w#Y$m-kfT9knV z5u6IDZnF9om=nDsxvw!xu8?bHH?wh)eF5>f;+;KXBCyOvxtAv306=`(y^#$$vpqLE zx!x6Ok9+p~-tO8)w$bV>o84Tbu%B|YV?v1gqR7*W@ehZ(<)69Hksp;%yc=%exV=) zW5S$AxgHD>Fr01zi2DY4g3cWM??lGAJMIod7t9z8>%g`S-0fD3_6a;!N!=uZfh!c0 z-8-l=$fi#OA<~K|iPvr#ulo2sT!NYFnos#O4!%Ju>Y2Tbz+|eSodS+zSEhd2H~}=T z!OWGAS-_2ROd5xa)p$iEDs<4$LY&~|u|-ZBe4{Iu6+azxxL zKFRF#Hu8GLv>OK3H3eZq()D6@mjf3?9bh19`jo6d`rdL(AZLkG%9CF7mVz@S)jMAv z<;W!LJ9CBw#bY!z$qd`Aj5Iw#tE{=ACCcQ=jO-=17wmgZMMFZGf5FXd=&T~yd)|O@ z-eZykoW{dj=##hBrBcAf0S+&`tKCK$f8gTmQrDVJ6q3pV$|Roe4+cWow{J<3eJyj# zI1}=aGASwqu2outHgS=>%-}s3X5Ij* zTY@e(nSSqmsOzyjk&m?F5a{8~8REQ4Vxl2ODoro@htV#v~4I0NeW`yfLJ7l!OCn*$;Gd8ft#>7X=%aD-`#$#jfe3i^5ECYnh4aCMbL+24{ zM($=VIiC3-KrTxjU~gMpX;Wa?W)-fs6pp1Nd7m?z9NgN93y~8Y!|T31^~k}zEduO# zWjq_$Ed>eIo!w-+J^%>r>TD|@1UpYZS+*9wuC<-vV%JtYR}FQZ=s;C_+pIym=D<3Y z?&jJq^wN*+Bua?H;Flb^o-Wos{vpTPI0VOfLa+cH2%<>vFT}ismsu5x_ zzqXG|4|F{DnXigrIrg7$k0X>u9MgJCin|Rl0Ye;m%-l&1c|Tuw9o?sZ+06^P1aXG}<9wo{U&0j}$(+(v~`1Ry!&&7G3FKgm#rrH`APz}OmvWF?a!-V-}_m01%v zwRp{KdfO*BTdsPUOoEa<_SKlm&uG`b`>ZmtN$K$j0g3~Sy>I zzl7zGpFnped!*GRxn?@#?%I3SuHD^pzh$@6w)4U}#FX|L@;N7$<3pbcHWCaZB%BHW zJmgJhH|3l;BsELsa@aSJxU9^vbt-2zNQs^B`OpNioiACFTM~3Ur^uaY_GrQ;V*R1- zc$wRPgqqg+F<^Fel)!bl!3){^Xo~O=1rdJg8EMReiYQE`6p%9&e#}F!iDYd>gj? z*8tR}4%r0MnnTR)bx;M!eILc2ap#GSPu$sa!H}H8{=`UEdP$)Ow0JF&E$&XRNFMQo zaNa)*F1Kn@w?HAyG@-cwEs6TcyP|&>Gd1NuE<8^geCip;0WpArdqx=v|#!3l~j!ungy{6 zbRZcYyAT~rHV2c?Tk?obp4&?L1-%jEenGFZ!kzQvjl3i=a-K(8U$a`!W#bYLH zY()}ruIqlibdt?7*N`~EB(L}M9IqwT=mc=CT|9blU1lSwdpFGh)PvBDi$qMWx7kf~ zJMxIOPObnnaB++`^`_&40NLjUO2Zv}-S>4(;_lrmKleX@787LCGD6tgVc6Q!`%Dgj z%n7Jb?@!A#-%xr4vLw{^ljk-49bogCxd?ac`Is${2>NG#djIra`KiD9^FPJX`PdA* zbefnP$M5Uo@jw&Y`-^ZS>5vtpZ}-hWT_?E_(h2AC{;4K+)2u)?CLkNX_q~rdm(WnP zS*8;|$DTzk`|fLoY`Y9KJKOyOLb{ppn(Go?d(XiV>l^fx?)27=23{J6xi^>D*~u&! za`a~VJ@h2W1^10+(mb5p+&9+;_Qm~*T@4c=%v^ilCP{YhMqw6UdCg|S?oIB^PRPBR zyI3cS%kDX;z)cr2cFFb(e$I`j0DCVd>;aO%B75`M_Zz0`#EZi#bzqa1n&d8R8wN+x zz>wXYQ*fN01ZVH=`$C%sdiZ6z=~{kpCimUl$=$nU$t~lD-J6){GdZ-0hPgW<+WXGV z+qCZN5H`wrOd!X&PP<2YeJ|__#BcZ^Uc7y6>VkfAJ@O`Y-;y|KdOU?SJ~a-}&|5_|5_O-fJd14#beY z*j?rTI91RbH+oOsK%fC(3qe)z-g5?gLqW?zp#gEX(_MgmZ#OnO<-|}0c2Cs}DCs>*C;?t$b5g72+k?WYqKNabHNIP6|+=8DS<{ zS5I)pEl-vtvd7&+vdIbZN~^)qbZ7|c% z`pc1~`=Lbg-KQZM-tpiy21d4cwiv&s} zoClAY@>Gsqj`yd%W*$lB^bpotak_?rHrwU1f^o-gEC} z$3a>O_MVuU8%6H>KI;f!nB|27$)={`k!b}x?X}^RgBRfI-s?QuDEgM|&1Gw$etwdi z>ZbKdXF!xGZ$8}FWaRKH*opH@%Ms>WFk$o3D+rA4z8XW~%((YGJENPF1ABMgw<_4v zdGg$Cd=#3_%YeOi-kvY8clM6Op{Il%56$t1?3QmJ*Y%c-L+S|Cr~b7&Gn;V2dyf36 zd$+#;6S(H}dd=G#$$jrNO}ozHhDlQ1Zdx`Zh|aMWoXDSWQ6T+P!XC5JHuZ}s5oRWPSKvz42!CC( zchjZsnIVs0Rb$C%x0N42iGZgkmoW-L*l3kx=futhk zXmQM4!oXPvi>bXD2Lf7zlXPBCKLsD2?7cb8dv~}b4%tIXNN}9g%P^bs*pFkEV?#L6 zAGn?@97z4Wa5r|A4>cq}e9DP4-Z7s-dbcxL}=9t{_>&M13a0 z@xU1V<2sf2;iO3VFH$7u_3bv%5j_~*ySbUs%rr_Mq$z;LtzSmLn z+RSxQj*`83-;{6;97l7TUoeUeY~Kcwy}WK-jxICYL9*E*)5&Q_Z-w05>m>J`c`Nv} zu(A{!?pMRz@vqvQ`3x`dcnXy2UW#cc%#;nAm`Lu;*oCp`R%XG50TaVL)MlA*&Pd1+ z1Au(G*XPLCj1TRTknJ3y3!$0o;b-Yum(KaL)&^%J8+N=TuX|^%Y6*v3q=rCq>;-_O zx(faE6>nj?!ku^(aOipn!FD50h$BhTZm;Xw6UR)T)oI}^;bB8V3izS^e%Q3N!gcil z#zD37g$fs`n^U4NC2+KC$Dz+c11jUOSvx#w{MhO!p4Fkv+YtEdm!yddbRcW}!tx|r zOdz#5korNAYo>L`Qh-0l(R;g@*FSx@MIOgDT)G~jA4Bx|IsJU~Coq;QLK8Js-;oWY z*a}Mc9A}Ud$@1{xG*0>f$x8d#!n^l|RD>$>jOeLv3$2!{p*vF&p5yJcJ~asdO^ zdCuRG-0V1nz%W~f;?$ENljAi52^6)eGq6XC-1X3S6`7qr#4Go3b=CK>v`&*|k|Vc| zx(6Nr03ZNKL_t)2wB91g9G=n{+);8*Sk4R=7gEKxX+06VPO_Nkl6dhAp2RmZjWZtJ z&#`Y!V4w^iN<-RVK+IfYBi^`oEEFf2mlu7W^u_cU`+%3+A>AG$;LK%XlD4i!eEbQp zbut11W0G{Jq;_bcITz^Obq89FZ@A_{ws))~CE{r<>3k$HcnUvrXGNAfYAr~*6BwK{ zrq6UDfb8BfQ1LpYa-4f#&NX9gUB%HQ!qU~V#=XIGhMvW7r2D?dzMsoR69yL8PG$(;93C`hMThgh5A z$Ily-SV`jPB?r&Tgk*`Q>iGQAKq~)!KVImff#_bIz>eWSKJ$w^daFUcOAvW6Yp6Cn$=Imnadic*N zkPy=)bb=OmU8&w7v5#~wJ_{$mWUkpHyEYmJ8ZIt32|KX&?aXvO z%LFQ6P2)_mp_VU<&?A$faSa%Uu9s4br==SZLUG#|qyZsK6QTKb3T(&K33fp&A&GOM zG{@1%xl700v=Madx$g$lyYiEajjKHvTzWn0L;Zhq60K=-I zix3@$1Ui56(CM+$C@qK-1k3(}?Crc;Tf2?-6eI136AjlAJDyQNPngTn(D{s5c9!dE z&H04sBS(lFTUnAOg{+;^_yVD_Lk^UL90~h=d#M9F@uEKqy@P2qOQJ}fvwESTq+$)X8ifX68Hhmh0RL3Cg_LP{Gls=GVRrg{?^=?%HA$riU;IpLrJw}Pcq zL;J{-E5XB~?N|wCE)81nwHD#5xd10j9tp@nTCo9VD)y$6>i$%cKldk$G&QAw&f$|IsM$ zIP;S=wMnSJ^orB+Sn6FlQo5XCs39!EyP%JcfFX+c+<*2yBrZB+lgBz`_4zyCTy*7d z=WLOT9)It2a%2wWv*XB!&1oA_dCypl{l^@;_c+f|(N!dB+36gNV|SRNcu$@dBwN@dfpEbCG7^!1*hn*0*y%H>}JO79fR#D zK8qbKRrB7?*?rAOua*10zy0lR|J+~sx!0X9zWm~g?|#`2lA@8MLA;_aY(hfzZUXB- z&^1IG$*5gJt*dOZo6SjI?ZmD!oWF7Svyb-={K&udAN;NV=wtZ!X}e^p(sZr$r_-ww zBE749*}QJ=n>+00US9X!%j>@Hm-n6f&b@gpukgD0 zc<0md>CWrU>*mwV$2+gre!BP5i;pipZ9ab5k1zl9(|)}7(`!Gy{B-Z1ee%}F^ltp{{kR|O(um#m=4E`$w9*z96jrf*)hs%bFJva40}9xu{e zF@bC5>(|?dx0#QxKm7gQ|6hLVxBk0d{k7lx&42hizw^7}hadW(AHCi#l5hRc5B~fw z{LJ6}yMOnu|INSo{XhJJfB4-${+ECFN3Tyi*Hckx1ZEVcr5TvX1h1>1-YUa5)?k+g z8>>(BYdkqrpP4C#pwoUCfSHTDzs{va|}yU{4iE zDP+sJb)J3{GR+Ra;iMFVQBzMeS*qIUZPBsxQ0<|dU7z$zb;jE{at^m7fnGm|@w`He)^ zb?xqqhf@VL==9lIVW1O)4LWZ2QKEKwmkAGZIK%{?7A%!3{~BkKKx4pxH;HoZGDSiN z4YMej0uY;JV=E{VQvTVw)QpWkM>mYS)a(c!HW~bYfGgiF<3^ZIh{bQDc z%ajXF4!!C}4EG(qJ`;cw384aEpcuKF`R4n0h-4*-<$~`_tLW9cDSyUJc^bxMT+PZk zq79M8iZdoWj)SeLvcTw8S(NLo?l&uGUQ1B~DqGojf*<65@(LK*%(gh{KOKn;7dhi5 zYGNa$$6{?Xoec>94P04_nc-Y?v zh-s)f@T8I)RCsp>fXJoY@+tNya)HqdDW;(jh8<@(XzW~+*F5%>p;=1?HZ+NCmrr{q z-5Ea33pA>Og^7i$2xH#g3R()XS)0y2M*6FYWW#Jh7047WMOqu*?}&@o~-v62Q2dWOsqNM!g>E_>U?&w z1}7R+v*!}XrprEMQ*y=$5%&&gyDc});;?l0%0uT02Bq-o^IB@4ls}&h0o3cy1^opa zr1kh?jSAwXi^8WDPoGC?r-NzN**MYlT-46rUm8l_ErSod0n|XvT(p6Hbo_!6tM5O@ zHqw0~9{OGbo!H9q@T-XzSivlEU3)t-bHuFvtm3(W29Jl^wAiW|eKdcJGrVTM>z zBjhl}mE5aL8f?no#Ag5Xa$x7WfP{zX<&^+hLuA?U&8ZqQlY1-XsG6i)@tAcIW+ZSG zl`XB(htVeU6<8{|3dixEeS{Lm5Uu#BJ(bSdL|ad2I)YAcO1G8l49+#Z2LKV)W;B_! zLU5BMjM;TYOq!oaVxGJfJnSxwD`TwzArLaSn`u7;oNFLSc2{S7n@oAT3D7O`A(w0z zhrDK7R=ucFQETN>Xij?X`@v^?k@|1WdPq#DcI0}Ha@d?5rcTADbW7*# zj&KZCJ32i(euyOK?x|OG>WRQO$xGQ7GDB}>0tqwMx{sgGcf*pv*s>>^WF%Qh7@RqB zI!+7Jpv;>wyP`w328FJFk2SDgsFoBtbPPjyzK_Tq=i|JgRl+PYM@*xkZ#r@Gcy6hT!Y)l#oiKnd*pcBlh6Le!v~0y zB-x~{@lf!|L=xnw!CBDwjjw<6`oSOgvG0BPJ+xZ48Gs!Xq0W6u7b6-ZmBLx@bPYon1fra8l{=s4I2^kmU}1!J%L0w*-Q5fY_0H-vJRJsCX$4GtJa@ARULM(Ow@Gr! zh?3!vC@CzEKoj^mdIl$TNN8D0a$40n<+0;Isohn+x*;4(@wDiOS&Qd87VSUbfwG?{ zYBmpal}SjlIuG8Vl2WFLjC1cPsQ~)}-7v-TbQ(3wo&C{ofBR>D=4b9ZUwrYs?|%1- zUP?T>zR6K0K-0R}Quq!{`AwQZ~e%R{ri9G z?{pVGSU)C;L+eEnPRXvxat~N+r~EwN6$C(sWX4U>U?eBaA7ba;XTI8o!D~#C#Rz$N z;IJg{q?14*N$!NsL%Iprd+4*r4UP=EaBh|$F}?W}NYaUU;wh@AmiuCG6EjzFY<@oy zANK&rz}K7)*Y%CJ55zzEmw)uX{?Gs0ul?FT_>KSJH~#tm^UptAAHMw~Km7K!50M}G z(I5GxU;cOgCR_s4%6mgbi+TDLucg-K_8_KoJ9nVH%^n76k+ zL!xkcY`|bKlJXXPr>Ve69!e&8HW3LtGuU9wduGFk4unn65~h;Il|BuiR+R=6RXWY1 zB4mQ~rofDqXhIc_rD1F|J&?cuGpBR))Dh`80zCfc!^3l+qkGPZg1p!H zJh0eGMip8ssz5hka{eCQ;k{<}E|SF>NGr(=nB|qR z)%DRBs0C+`69rBuK=FLy!x_J-&_GcQl>)9L%oM>h9FOw-oef9CYK@+lHrJ&-%MWen zo=4f`Frl(*(nmBK4`|DOrr1(dZp`2c~^Ejkw>P%P^YXG-hs7rXe0zWSRm#1^OHOkbkHpOWY5Nk zR@8ay`^c2oFAHm&G&L)ML`P81hoW<5Q4^SWJmOFp@)+{;gI$Q(uaw6R4+xx@+|kXk zD$_&W=RymcWw(_HV0geTZh7o~FxHUimC1~1RLMdY&se)^i!-ODslXk-9|2n1)3I@4 z`gd8>F51siN1f61Xn7QA2ejwQR=@?t1OCh$3o8OOWat&OCfEQ@YmamJ8EjT(Y1}|P zp#wN^)9p4-@|7@E^VP>Ya@&4xxLF8k^g7XuAcl-ib4Pmv%a_WpIe6JJFGS9azKC?G zbTS2C-G;mj~u{Gy-)GEV!Up4i z;!xp*iyQM?Q-7hCDb^fBx+JLcb|u|^q5L6PW5++@Ff%6L z%E;b3bBJp?0L;ifYJmgo=qMjkjvaN`ZPs{3mU5bsI&dxodN_cCT$D|CJfR3vm_u-y z#Ea807l$yT1gW$cXpHzw3tEg2#?Nr)$dS0^Xo@G(4Hq+z-b zdq${&8YhiJ+U1uMGz*791xxAC#znSB5F`=jD>uD}dHOgBs8@gWpPSQ79!zp-<=DwG zfoTc!IetPXp`a zu>JHln_3FmP^}OZ!<cdt06DIzVP-(!;(kZ1bRxaX$}9AjI;maBboVomabS!yNF6R^vk_$_V2i zGqch-ml|&eX$hOcaW!tt zU?kKbK&*i(q*_h_>GC1*;Ez_OIP&>!`jVur+`SupZR%Ip8u;KdXTiIyNUlg)``IIT0oBMzwA{Zn)LDL9y>1MWV+U$mNqQTKt8 z&tXae8%{oA7`49+Pe~x>A+}{~)FaFAydwGzeWA$F%~0?U3~GD0G_rf1O;v*dmI$PI zbrc{W4ONr?$2ybG_C*>t>C-8A zY)aBtAXoGG`+{4C79GK4#}CA?A=l}$efN(#7J!1;8uJJRq%db2g}VNU?WDMBk}5HF zzj*|8y(cDt5^8j2uH;4F-4etDaV^+P}Qy)VAk z!*XDJ2eeD%g7&kXhdT@`IVO36vzL6HC)7ekglAZR?ZoK3@zt8+ynP1ELp_|goR~HV zj+|4T=g~TqKqsB%ShLxIRjefrB^v8hC$QD9R|ZDxUbS6udzezHotFraa=R;MgPRWb zT+*aM2awJU40;mQnM|~WPM>v2$kDV~gQg}tlOib99^nq40Cie|hb|+{bJ6XyeG(c0 zVKN>@P64VoB5ueq2tIZh4+*G!g$LMU2m#XT`{yA$&rd)d8jCH)&&ReI)P%8Ph?ZC6 z095Tgd8;x%0yHg~z!}A4Gfu$bt*8_YrZD%ypKP5jiWu**>ghW-f&6%)xSZuemU=_3j7lt-3d7EvK+-cD5lq z1&9iM_=|)id;jBvO$*lpPQrmT(1vZ0Ym7YcWE!#B%mFrAU=IP7j03mLj)~y=DK=6o zbE-XIgm62K)0Gn?$xJ*Cce==_dy+f3zy0lR|Lo8H?59`0``zz;@x_-k7AGk&wroqX zJMca(}pmc*9*@?aVafldLleEgX2`{5t`5C8Vx`M7+#H%`_C z%D026hXN!C4N_-R^Z!%zu0Pjh*>%_NH}_LjT`u=Ewulfy;Tlj(J|F~05s<{f_$L0} zsIiS>+4uu;P!K}m@+t0aU)t%m>8fsbJ$ns4jJeNSId9ebp0m&1Yt1?4n3py8<@B_D zy1`RXwc?dFW$*5jhVBHz3~;!UYFHm2z$Ba)41La_V4s4UdQGvW*AGzqI|Ve9RD>># zh@w=)xwd46K)XC|<0l_ofAssm`{N&e?`zi~IF1;Yixuq%(+l}@FXrY%sGBDw%%Cy# z%ux2_Szy8>XDLN+zt7rGK6;y9`0TT5u0Q+1_rLh!^N|)Nc!x7_uMDvgwVusO0bZ7?aFN?rD0CZMo!uc>?3Lu7rZE@;iLmG zO>gC1`9o?QdEpVm+g@h_xt;#1bm{FRd)CuwTCWqB-XR2ZJV-f6Kz#%|=hd7g@Pe8a ziJZ((p@~TqIV23p=~2lcC+7^2vjOC^YDu^yF-aQwkQ(`Hxuy8gTpz!^eH8xYuYUBu z|H+^H-aq@DKm3C~`re=Z;FFJk;oIN&CB{qt?!Wza|LL#(H^2HDzxnU{y?^(|KmFp* z|Kcxy_P0ONd7IHBGxj=HRG74Q7{GYlxlu-1+sxoJ^w2|!n>E9QbJD7&pcS20=8Vlk z2^_o*fl-|yYKrDYCIIU%0Mn$UdrZCl@Vu!TC-wQm5VJE=mK=F}*2k`?6?xu>rJc@E z$jo#gt&}H+dwi3@b{k3J6WbhRdQSJzCW}VG&Kkp~AKak~#I z`wTd#6W(X0yJ-VR&j{8E3it$${ip?Lay7MKTR+B%d)Z(=*gaRa!RM?}X09ve45|oi zoH0o4J98Zj>d^I|aq1;C-p~_)Bpe#>OELTQ)YpLuy)$`tdnr?70OzDj`G`X-3!dd; zjTfxB5o0zffFT*n{6(u)6lWV)Nfj=2h-1+va~)$SKe5Y>1J@ZfoHGa zE9LD>Ig&oQW4*-v)lPt`H+l4ww&cSl z<1_t-gA?^OvDBsa#KeT9PGxKQR+HmQ^BQ@yaIM4^cq`6s6CfFwbDd1tCw09Hdo|>= zgj{X#oMe_GI)~%HwDlBP`zf5{=p`ji9MyB)wSj-RN3?OnEbz}ufk3Rdi&4}}95(!! zl$cl5RNiuG-vcx1Ji!g>*&J;oGu2DQl_C_tInW}m^7Xa8mdGYmn)Sw3v}rWK#`B|U z@=iBCX4rqyk%cv_xy0C&NIDS2wh%}+B2JKlu)Tw$wq2y-m4na8ClEricz-3jkRZ-1 zLO=%)BB;h33Z9Sf3znEB(WD-Yw!P}xoehT5d!LL2J<#9^fh)inp> zbbsM#e?I=3AZMdjZ%0hV89m;HwiGWk9J9n~1zYi@XXO<*>vlrOh>G;|q-y|5p}x&C zM!`?^4kSLjO zgv9YlepMSdIZSa%pagKVP89%4(XnK}CFe6(|Hip+-~dx8aatSicERlO+82lQ%xRPC zkZ@fm2ec=0t{9T@+CQB1L?Cvg8B2+r6)aMcH|Y!)NhT)EYu5-y8M(#%JgJ}$@OXO~-jE+p);sX#{ z!aAnOH`R$TD|Y4n@Y*Bm^CXBmj42N1N_`tSd3gZQ${%H$V_uE1>7tmE#!s%t7Kid? zb-u<4#{@ZnotqCI`I0btM7-03ZNKL_t)X zf^*17@B(t2wGj!4xM_EO)P$#BC&Jn1_ngcr6)JJ= z3>QE-rU0vM7et~gAEe$q<4k9x21=GwLFwuT$WsdPjMMQ6K2^c=Cybq(#SwFIUw#C- z_Ih$q3}aIWsf107ZoWI1j!%{_(1CNfGsPAiBE~gcQ4d@qQRnhZmM&pVnDLyu_R(`m znpSf{-j)tg@QUNb;f&DbK)=_`HGvr0XBZgbk_Aj}b{-whJuXA%F9FMT)kGe*1h#HK%Wo8aG zcO0B@ZJ!FpBsWNGLYX8^;E*nhi|3q^xj5M$5+Df4gtT{*gT4%Vfs!;`Go0JO85V`e zWjR)ulr9hgOk0hO!(J!EBwY_wG6@orWAlhcEc@8NPYHnP>GN3euyIm4MTpRNw0=(c zF&q4fPv>`%vrUGXB)49{C6AUFnd7?P3AH1^oOAjUlY|$S3;`@|Hkwz5+CO-iT6o20 zbRO&ov)W^Xm9}U3i`M2fiX~11Br-}CrUPiFnMPWa`$!m*RL3C6CDbU+NlG;B9YIx+ zKD9GlWcO%5aZWl{RY*l|Rv6>SMSC%sFq#uC!%0sMiBp(*M|ub|3yI5uL(=FZMuu5p zrYWFd(BfaT13Z5@Z={T;nE1A#={XXYy1~{~47@LBCDsN5S`Wa!u)tRa!z=>zVn{&^3I6kvh z&>0(kKt`xvKc-bZd4H)-9w`PuF4mg1!6gGV+`Fs~dC;qvAC74gsmt%_m_YeJ0QYuQ z`Y|XR1tvKohf~bk|KXgEzxK)Xjc@$Y=bwL}qasCV#;}F8NQZ1@?p)pGjE2+W=C`$&iU}?4*i=Zxj9~?;<%KLkbvVTIeg$)B$$!T(!K$59Dca|L5{7YG14IuI5 zZEL<2Vpx}o>&|mh%@!`lZRiZ zroxma3=*&*t^OCBDuK1dVj2^03kNjhV4dhhI9hKhwihq5&4;~7>>l|@q4R~nvt zO>tTyeu(La8)UHt9qLLpTozc8#$FIj$5{n+9A}&q0Ia%pitbilfZbXxIH1;!1J45( zh&-EDPIQ}PzW4^pjM=PG_1C$fJY1RPnYrrFWb(lgbUZoQvXNJDC#ejnqmG_}*#prDH|g*u(y>Qk9m z`&U~bo$N{5vQxxp2#M43Rmp~n1QssUwaaL@&(e(UiLT=`J{)AmbD!^g`#b;OSAOOF z&gY+h{*#}44h%L95ONNkzIdiz)Ne>}O;rmiZMZ||wXV~<8!Aqbm6 z_v`Q|L?;bd%vW=&O($@|nIw`L=8=?hGI~ynDs%I84arF_1}86p9OcuG<_~}WcYgfC z?|posLwUl4<8?t7UELia;j$t>c6^(P`&0?&TpFuaHPe7&iWpg(5e$d((c8zL{o*go z_4fTAeD8}detIEH?xYVEIPXc$5(dIalDyxyA-U6qU+x<8{rw#e_xqi5Z2^#zmqJbeFVDFz_ZiOh^qGbWDNiXC-MpO|^R==oJlKLJ zcn#0Q%LwIwft-^NY0F-yvz~`=b;JYFu(eA z)mG}kssru=X}FWE*}&#^71Y5CKlb|C#~%wn{^5`Qw}0_3zx&`7kG}bh zZ+!ck-?;VD&p!S8|KRWc_W$^s|LDK{$6x#O0FFR$ziWT_!@vBifBjeQ30`+XVbQu+ ziF?Ap%;+`avy!c#<8{?9)(}N0vWF}$c|1pJV&)0IBd6h!uyqCsY`V3hUFIA|;Xrep zsFTF~9d}gr;vE45O{hN}qI;7Q>uKv>&)#W?!E1o=`KhuJ2_N@fz=c~|@ZbQq9?S#$x0Kr=6Qzv%*nk#5;xDL zu=hCP>~DCX9;gbgv;|tw?T$J2(T**=(Rs?q(OhE{3XpVOJ1Q>!4KhxWHinW=KkKb* zr-Xwbwq@9QteR($7PzKWnJLf;(1ep{b~+s)Jtn~>IB1qn_qxI)Vym1X(7LMC0bO@edcYF3>vWfJl0oA1J;O zZ^F#mDn`gP)8*B;{#HGmX@HGh)o+-yiKEn7ftA-S;wyjsP3spGYM45vOe+`?cbMJj zzTamiMdvYJ9S%}T&q%t%zOnk0NY8}1qD;yHC95i>Kyv<;xuBam&FH1vGnmZOjp8`gu)6Hcdy2h!7ZIaO91 z2}l+eg@(cXsnK~5Lm_wxDe1ZrEONaY^u%jAOHx4T?(}HCBAETjnM%ftz_R`;Jngig z8d3rz+h4Y>+|BE%SHOmM1P|{pj(}@b0ZPl6kwywqS&kxOK=UlVRz|rL!>_fTK4PI&5O%64Z#E=bxsiXDFAtAH^7hqCUAF!UMBZ$ddJ>e}ObdOE< zW!h*qOT_2%=A-UGs?JH$IJ16?B<1atq4WSsgd|pvhLJ2k*BzD!@H%pj4jn#(k&v>e z%ZZwk)p*nu6F{BDm(lmU?_JJzuM&gBjpL>pI%HD19<>5f`ryoLUEHx)iGbv;Uk9B6 z3>~f>ndoa`F>G97hD5vMCz0zCdJ5USC!TC5Fr>i{Je0}Q5|xA!QXwW`p!sh9Fb+9w zj&H%XD!r`XxiiyqfzM&+bj4k`IxFLq$LPs&2_M3MPucGX&okckXo${~_9&;6OG4K* zb(4zLaM8RlPB#Ixn>}-q3z@C=0K$xi^3~$?nirnpA+QiSPr051*N7V1E_gARz{V2J znajBAN(XQ~g0Tf2OPPLbuv-pQkq%{q&_fZ2K2otM39EJodZ5m(&yujn2-GdAcK@lr za83gzRk*WLndI zglDaE!mFK7s%zcqtAntLIF8f(5ja<@dPfsSPnV~iTItLjH8cftP(3{>Q@?JaqW9Ae z@W3>od4~jNbkSLTd}eEC>y)p+=GHfJT}fsPd7Ps2UdioT&;`_h4mu@R*mzi;l#b6U z0#CPpwQ!QN;RSmVYb39TASK>w%=f-w4OXm(ErjBskpWu2oIBSwWmX$Y7U#WdJo041 z4%_JrFunn$Yi`C#j#yQ$t9fd*11Qc5xc#CMR#p@;bEP*tIJBDj5O3X`tCP<{wV`0k z0jwjBFv>8Ve)Jr8v~(6e$dj-jkh<7_=iEAtW7qYTbDWO9N)+pWmo^^aUVxiNt$EV& zsdx~s%M{bap^W14+7aOZ^`(Gu+V?97qLXYM**zw1{t}~e-(R*LuG)nOPzU@#KdJeB zu4YDp5@0V*ttJ3N$B+j;$VsN)AwsbJ+em&s_}znYFE}{mPxlX}Q`fDab^BU<7+|hl zm!?*8M%zvW%ZHHSDbl*>pw=x)_d&2=N)tu}t3XvN)ig=bs^cYSe?f&QABd3LNs6Ci z-C@Ra-!oH~)v9RoP>DyFX#2nn z*?!bo^Heba^kE!!-o>+UX?eJ2>k(QrkxIYodLhl5U6+^91%ZL5%n@Mpozy8CQArdN z=)-J;8Liqg^E^x|f(!|}Gy?)2-sf4k^;IR)^7SwkoyPg{P5La@K*i9U%*@?zO4DVg zPnBB{`{?UXi?@|xl%YU%hF&;tb4LwXL4#jaIEdX@f@Z& zUrvJ~BAne39)sgZnjKTrO_GsT8*9f}tJ}-w`g_Oi8y?K}V~}r%Avm>U zuW9^%KMRr07RPp`;!y&S&9v^6Dl$I5e{6%M?)-tdQlm@<*r>V?P+Y2rZrTf@#nCgD!5HW%UU_ir+*moUwuS*9riZLRwHfQ)jwJ5~=UG|BnSn9wrMhmb zf!^m(%Yx9{B$AD07f<@oTA#Cz!nlY@U~+Ik#`CqyKmLQ?`|)4=;NxBw04W!ZuBW?J z2JKTWvgKbONf$vwdRei~tNPBJH+GyNHF0=?KDO-ZD3qT~Df z+S_WM*nZ04VQ%4vaS6?L?YVUW|c;#?>?j`{&=O?WoeOl91J ztehv^KWX<38XkN0LP&k{tlP?=7YRoo<4g~i$`zyw^OaJq*%T*XXtav9%#}T>_Ygp> z!;Vr}VOE{((3j4lndf6dc@CQ7vM*@GG^n2aS(!S?rkQb$_57)2tCEm_dwLrf z9Z=Ks4LTSQ&O`Q&5s6Mhy$vj}nK&1H6lbDUC&09D?Fd-CT;Xf%_1qFS=k^*$cGcyC zo@_KoNroZ_^IF%WH%SdaoO#_DSsg-_0L6jw8CMfU#$g4;@J_t?$iyPj&A2Q5Gv*z` z#in&c?|4mm2ijE70XWRe**md7p{(R2fL`J{CMNxT=%@ z=4+69B*t-^z;ZL7Ng^}$S`BCMNwW58*mgq8LF*9s(BOh(El zN8dV5)k`WvIfE)e*5c5?X0Yr1Ky)~bbTcg&6P&FwZ}2ityN4nWB0AF`b}Zl)XuKD| z?se@^Zd?!1MzsS|f9&#I40q}u9bpMF;~OcJy64=zKXrnUS)c9!;|ys5GbRUf$aGZD zkqNij3k4M57)DNJp1M6qa&pa7@`|6@L2xk0dDPrUv!Z*$Nv;_ZhF<=ub+G{v+)U-` z8rG;7XAHhw%{sw!u^o{1ngg^{KxRy8@Uea?Jl(*gQNHORZ_G1qe~68lSG`wW^<(h~ zG8$5pL&K9;$(V`(vj@PbJJ_euRPGLi;N&ou)HLFdbDZTcr0|NnV5CW zb56Suq(zl$m!$`^7JK=x>0ZU*1X~F1j5AbgR_@l5)$WnX%NEik@p&?nUK&r;du%3P z(q+OUk{=4C5*^Aq80sEKE4On~dlgJq!}QKV9Ooj}cq-fJg7ZcHz@@enlCJ6k83t-3 zRJ8efz<)#U^d^eRU%&PN5F%h*D5(s@;Ut$s7V-o!p4o9uv?sliQ=`i{2g^oqP|TH3 zq9({xHwysRap(Uk}AL1r1+fz(;ZhR2)nI_ApOaej+}#xnB|+5bESM>EVRJcs8dN^J3MyW zKkU}Nc56*JG$$tYjZmtDc#_Uj%HsAz@(k!>d0zV1zXGP&HHW$ybL9*$GhTVF4mT9P zXRL(I$H`$6?=#0t80(3PY%C5ok_9xUg-NBI!2-W{+_#Z-)h?g6rT2a;~jadO{j8>!~r?UDf+cmpYK3);*5L-WxsNm3YNW}NZlc80o%1`tf{ zbCEj$Gs8n|I4+*g{p_N%&n7u0nVCkp(Rh%;vj)P=n?vvgqO{_@-J}i}j}Zs&nfm)R z*OBZ3YXgXND0i@j2G>aASW0q}Avtm!6HYA*!DP8{==3@#Hd=wy{%YMgNOH!(nDTU& zlkoh0@$x-z2-3_MLBly1AmcE#y0+mzz2$7CG#A6x%OLg9@>^8zUR>qiO~k8qKT$5+ zFusP+q`(XyqgzpQ*ildGRg3e<2aZ0kVh89zlG7)*i`)>>fO2feoKO-LF0h&4Ahr9H zkPcLKY8N#e(kCCst40_nMR%HotBoC`5}KSDdzZZfj|`2Ihmgj{bcJRO0pniHRh91h zTr=RbnOS}UY?!HQKhjD9NNgBsqCT7488Xv$RiS>~es@x0h zE(dn5WX}ti7UVRpbjbmd^k%K`0zLOgV?ZkD{tm-X%$* zjf8FtREUemTC8kO>LGTbj(^Rxa2N|@kyr?UU1ZkquUB}0EtOMO={{d+&Srg#7gf8Y z0$hW3x-m3nO;Jl%Fr75918Bo@DrVyJhIQQbk~E-d)^)5=9|T!2YnS~jc12V0Y5kFg z>8@#1WdM6{#=1V0Gu2}VVb?^-6aJ?1j9RQ_9_>2ZGEHB6Y;-cp-ShXzL+6fjl5sV8 zB-Yh%lC!cslN1D%balk;y%02MONs#HOs7i=vJ;(fMknxiLFr^Q5o+!P`pIsFgaF0> z$=-_B6)25Dt0@T@qp$K>mghl0mw~Uc30L3B{mAeH<7t=^BCP>K$U_j(!!9ja{_cPZ zF{A}E5du@hI@wAE+9>E@cZYSv=JC=y)Tn}?A5$m4jL-ky!J1LLHH#+uxMG2_G zHkH!Z!I>*aj?>6P%>kUVKVk5G?w@|~)4%?k|8La9B*-Iqfvt^5p!{2{pKnEyLz`Hf z5P~~pP7c7igJvtulc?uDB!~NFU!Jdj>zDuOul>fC$1lIk+3BmhfzEi6-pGCq**0i} z01h3vR?p#4K`(lHB?nR%fCst-7=fI$uz3t%hkYmpP_Cv*Ldf8i``A1R#}HEP6&!F( zS~Jyl1Idb76akXFU4HrzfAr6P_s4(v{Z9s`K?S^*Jc{!0cInQ@TE3X) zwy^@qDWy=m^nsG056enNOCaRLJYDSa;n@8bpD9s3n)&FuKKbb5_b=~1{a63$5C7l~ z{^#HMo&Wp)`hP$E_~T#x<$vp=k1s#^=s)<6{=NUbjt7v<1;NGH@%fg7*Z(vUrd>A{tn( z5Vm+-l~>TMGrKaHfQHDhp1K)i|L8e&(A2Ej3G6JL?5TsY+U(BHzEAR6ol@D^9nIBK zcUd%QKy>sFT6okaV{AOhIZap_^(>B+@-=zknt_ep7Nmu7$}FVou4&q@WZgsQz=Drr z4UMJfGua-~LV{zeflw$B7*0>&7}ia-N8vb!#B(wBKKhjwtY)>~wNN^)Vu#{3ruUKp zFkLb$>Atj3m>C~k*2}VLsC%AZ>uub95Y;@3qN-J~(X%J>(m=5|V_;?)XBq=KPoDc2 z@ELEeb8Z!;>B=zSE;rw!bt1n~sM42^9L8;byuRo+j}|l2_@SKB!IlrNovhjqaztO$ zD+GNJB*8v`Aa%=*&}8fu(5+lr<`5Qud8DkZd~8~+4Di@(RvgqBcJc}C%0Q`WJ4nYh zt$(Nl30-(~4wKZUbfZy*(xSy(W$J7tD#UHup*7|nsy(L2fTrd4Yr;p(g;`1LAsd`V zCniZXt001UPOcBci_9s&38|Yd-}a886Kd$4U9#TcW8$Egm8GK8K4#w#%BE2N;n6zD z2iGj*&yYDXZqkX@mP|qi+Tnv!AlN`rD6>58*lc#eC88jJ9PZ1BgQ?^LGfxC-^)Qty zEElD#wmkYfbb_;bv50{@-NxOXr1b+@zFT2%vsQW5Z3V`vj8hruU-m_{hZqjYLP7V< zvu@=YPY@2doWl|O1cmxp+VEeWt4Ow{s+i|K*Ssi6sxQ>gbtK1nbhZw8M%rV#|06$A zBH(=v)Hxy(&gveWF$pU1DU^iN2yHdO%O~n?c?h_BLp``3oaClP-Ced2*r~b~ef53N z8QPbbt0C(#uCj)1N%6c5DSoymKvC+rmmp}R8>e9}VWS-|)J@k+NsO%hYN}3nlDTF- zb=pqcfu@>d8P;?*1DUU=eJahRr_nx$n`zUb)CIQh+Y}8o=bBl3l$b^>V`yVN53fF$ zM@X%gnxx-6D1Z$lJ+6VcZ#oqxNRk}b#ZG?Jh<}-5GVis0pL@eZ#I914>9j zU>YJ?v9BpAo@zoJpz=QBg9I8;*}-}gJ)pirS_2@Z?FUQoj2unUiIPLOm`DRnyQ!yk zXLYQbt|Hk1n>FIQdpZUUnyp{7v<@>i+3BSH_RvE@pfl}1vLWD^uQDS_W~K`o`o)wV zJ3ZY^4}?_4iZ$)QQYh90fpF_}pXX#^BhQmsfUg9W{jBZ6wfF~Yc12e!`n83sBh(1M z;B&F52S$ep3_}V$C1h1vV^z6?hWA}=)w4Ryx@?2|@?pPfRL7Ad$v8QUzVl*^tdL1+ z=!*(VNd@O7vbd7 zPd~Xn`^I;E`o$NzIsIXRWsr%Q0Z$t!0Db0j5^#ET(VpuonBpvA~25V6IsI)QZt-2czy99#Gd11Sj%PXg@2+D%0%h%Ct1t27y zx0ZTn(Vqqp3uCR^y(@hZ*H0m^H%UMf52)uHTF;@&no1SO$Y?gF9t#1FzmQs~kexej zfUItz93WKN$K3)Hl7@F(+cf3}i*yiZA&>f+_fH7ToydxS+A^S1xYGr z^)}==OywzDq?TI|#CEI}f&F@i7wS50e3%i=eV_0Aop1faU-^gk{r>q+zWAG;{N$R; z;ZB6@x~@SoPD0LUo?dAavJQ8jwgaiRCBZuWdQVRV8}D=42??j?E&z{z`~LpfH-G7$ z{Mv7PdHCBK9MT{_4)@79rWVEM2{6D3Yh261^7e%{8rOi2?UQaQ_!wkYzD9A@o=F-v z%h;ZuT|r`hMzpilRH+H3gT}5J3?s(`B1~o)CsTAO4Z=)v98Es?*gyXL-~F4v{Qk#p z(aA|4i<{uy5EB`QhQ3B8PQvxbT)cqe%sA2joAn3wVT$ z{m}9brP~HveaSkT8X_uW!hxP9NH`T^?UlM&N~e87bB>U6O35-V_NN^lx(#{Ben?I@ zsUQyphSO^`y1+%qN%G`-1m_z3`0efEx3@1p|LOnpfBc`n``!QP5C8ce{OCu2{q1jm z`AeDQ1e%IAVd<%K>09#;80&?#9o0Dj^$7HQb5X|c2cTqfO0@t9QtNI?Swh8RtjQ> zy1StrL<8=UVyEsD5D0UvS}6>q#%~AZ+1Zf|E|bN|LteWzuML@fCiAdjQenhYz`BsV zjNjFV7%zn@++E#O605@~1X^IIN!|9MU5tK$c$P=O>D1MhVeINLlfOCdbrk8VuPc? z!4$+n!@!U{jo>77i2yzXv#PF#rF@IPRW@lRj(X8EbGscUj56n>^$d@GxwIlz9;nu< za)CHcgafcR{-W{NqW=LG2A$-j zq}q7&C~}gS*~=DF+t*mwE3;pSvQsPpE#D>^4Uns@v~%~^(FmdXfD=w@i!01hhPd>)#?1Ii@(?D4fJ ziGqj7OuCKU`ZY_|OvORQi_>`}Ha|LbWFFavUFD|+%m5J)oc5?1CPlAaS3$U+-+DiE zL`0I_v{t`6Itd(znGB2DqEgcU(n_iKTa>rNK}Ik$HI3^zIF0_JroJ0D(DtNYQJ-la zqnK#(2oxlXLyQL&;iQg8DCG$vTMV1PR{y$QcM=>SB!?!BlkK6C z3^otS$)?O@re%Y@OIRZNlSXkCpa|m|l5lc4XTr2Ygac?f1R+IvgD5zMoen-tY6g&vjqd z-sib~9P3!?T>ISnlA)|8IiNJkAv8$DctaYtsgdzm1E_h0{_Q{lBd27hnx}eB@MWf} zKH*feGnK!f)q~VD7(a^obfP$=v&ZiZKB4iBORcD+Nf<1(As-6QHQAQ&0<|oK3Uo4{ zCQIGW1BsoS#*hyX_To}bImF=Lxh2gEtb^4??fTT`t7`>zrmReyB6XjSq zARh^eOU;SXLpl@YdI_ll{qbX?8e#&Mv$;VDBV=!#xFld|xx})QAw_{^hdjR@5bv0X zC8U_`o!<%ha2u9X^h#VfyAK0yM9$ZQ%@Z1IZl}EFoLyxRdO8;5$M4z zvzF|iQCjCDkR<0_0~ea}yuDSF#!iKyhG{`mpO$hvr-xvsH@b`;7W@#U7=BRr07DTH zZ4fIl$t5sUSvMJgeZJbPxIJR7j-wEgpfhq*=szZzI8Pxq8d%|JYtz}|);u!H*j3Q^ zut&DRH0ct^M(HLaWfS=DIhzp7L17&5Da)C8mJg+6zC>1saL!%bNl4mfI94m`M2zj; zID<9YZi-{3&x9K*(g_8TJW4Skg|cu%Y?_oVsvn^|`Hmz&hB>2g$-st880~#0b+{(n zeYi!78JWZxAU65*wbvfdRUzk~(PM?Zbw-mM69-9Y?}_y|*R!>NaQ-g2&Mwl!vSjAT zceOu(T+e(ALn6UKH(`1FtBX8&q?ikbR&&v-O{-m^XNh6Ll+liV)g<*%mCEg;J)Hzl zm`XX~XrBx`z{HH2E}?;5cE_0##&k>oO%^kA6+PjjK#Sl^g+B^%ETL1$_URLalPpMH zKp18)CI=W%b~oLw&U z+rPmiI)FMe*{Wi~b&W!+lVoHY{4G8>0vu>>uEUI}zI{J% z#tAh!*wGQ10hQXg>YnL9tV&uxH5t>BQ)Sc0Y?iDj5J>jACL75YT-81!*~*Zl^X|ra zk4p0mdCD1}v#rs;DW~OBq6s8(5<^Kgqw3u!9U-je{qiQ~c%W;FB$6;wZ!$H2a{fJs zKV|?VDR!F&j(1M*)W%q^Y2R;n^uu)gV?2Q9DEac=879ngib`;fiyk1s)UdGRF~0Yh!CPaZ}N93<#aB?hZ+q14ia)kEZ^e z>u5c*_pNrVUORH^6P`fr8@2Z{oypUtscVEYMZ>5Ra*UZxh^-ZxFnH}b-lEGtt4(jIIN2rNo zubof?wxUa@K#kxe9&^^AB8;I(?tov4hHM=%OU#+N##cQkB_}FZ`B5_r3oI*u!Z17$l^6)oLcd>4pQ_`Y4-3XH&_^F4~<<7Uu!k-4;E4zd%Tm zBh$!NBWitNoIU|y}t9U>+R)wdzo*&UEg}W z-uma$_4es{{dB#3n%BPQo3GcWm+S4*_4;zXewx?UdHXb<`{MO_{dB#)TyHPe>+8I| zoT0QkH~SSp_01$3C=4DP;n)=>A9Yx(_}Y^MHmhXmjRH9sBeA%Bk9;yEW0G087$E z&eUr;t>1Bam_}IAoNI|I=~T&{@W|=~r!Tad+JW-q=D1~Xh7Ek=m+XCSBF2Vuf`-`Z zYNtNAy2vN3NF`@as8AoU8|K8jmTH?&;-xS0`P17c=GT7h*M9D2|H@zZng8(@{`xPx z%**fjkso<`d%4~|{r*4jqkr;G{?t$Y(SPav{`znI;xGRC_x^8Ru3;j29Ce;BR=t3h z+UbM4EjIC?_wu0#=h1wX__)i$`FMwpIP(0syoP;+y=TxClYN+Xrf zy5;PDm#YPmCJT9)E8)3vcP!ELyx{d}G*()rf1XfT`Wiah&7E=C6pqS`rGI zq%IDj7ke5POHKG;oG>rqS@0GJBVAr;mjyINd*wq&a)hXIRRwi7I&>YYNe(uNW5_ri z!NyU(h+@;RQvu-80OQ>I_0?rdgHFhK-Gl3&M}7ci2-(avFbVigK`gpIrA&^tBJyt$?l1gRl8^^SqW%ToqiY_NDWKVy9y58o^bXp!= z(pY0vW5j9sEy{LJ{6rlm8^`3a?`5{Pjn%QG@fV3uTz!-oKpiojhzd#46-DLm=CgiQ zz*k1CAHB1~dEeGQlH#^USo_48&F-GQVy*t=>s^ZUn$?sm%lP8B%h;TM%y^a%m23uj30)q$>03;Xm}Rgw%A z6len~!WKHH6GJ?$`HIqMvb}O8;n5Z)e6R$)9K}IZ&dFGx7Io(U0OmjoE}nqu`pBB9 zr`s{0%1(uS2I>3#Uu@_etM^UC!{;BD+j@q|!w)HJkO@=5takNpMb{G#0L5or{v4LR zIBIp|4@E0X&p#eK_D6+e=0b1{8@f}_ah#;}b_Y1>j}qfT@7nBPPP>m4Gu7+-JZxNH z>ainuOV$KDuSX$rRUb9U!=Z&SEXO)<^;zpqAltCd2y>1nM^3tvzUf6h^&Zvk>deO~ z-&o%PIMpygCnzoa9c^GM#rL#qRdf0?8_mnihuV#CWu`o#qAm{|%|3NY%&7GX354>iQh_-EI@JR?iLF|? zp-te1i8zk7wSKJKnTL>rA8HANQd0Yh7H>60$uAp7xvYCsD{KqPhGm(J!`0y<3^0{k zLRX~#7jrHo^uD<1gr_h^(mEC$weX-(-*!QdFU}!jcA05payj8fCA&EmZyU-u^29A1 zJprtxO@(IAWW}8mr)KSk^!)7R2`0xjNQZIX@3H2Z8M7Ze@-ZI`@amZ2`K*Hqc;GPE zNj;kMxobthu>(E4is^k+sPyle%HR_yxwJMA@g06YVT|*^WpJ7lc;iKjum2~h}X#`4B z$Ns%8a6af6!I`ki@Dgy6tq(kSqVsE+eBAq1nn+52+y0LM=J0@OJBtrIhQp90p2jdX zbCJHU;*iDt*5_~ed;W~|^vgcH*4I;qRwP;c;tGqMz1}>Mwa-35h z+s$0LZ#26z(*%Z@>&pA>Sp#@*I&jctHZ#XS=baN>kqIe?QTEKN(sr#la`)W86KNeK z0Ee^a$Vk#4Db2LN5|sO~Lq~!@*=;<*DOcF;b4=bJ$(}Q6YY%bCNi#sBUo9kat^lD; z1|+@I7jd(F1pOCLo*!F$jA@BXzR6bA3rG&0B3-TR?scvN+qs$)ckh{N6G^L{Bp)6O zIWY+fa{R~!qDJzJd1kNXxH;cjrm#Q)xns~Se#5BL__U-w$m*7 zp?N6>WS1Gpu}VY+k{WiI>s-KI;<3?DD`_QHixE<6Q}|&e?v&%Xo$Jod4EK}02$aZZ zWv+&`JBr3Ti_F^CJkw>H<&wMaXVd!7iP(#gy`)jy@@2J5G~v?PJpsO?qNlEIc@aXQ zm*g~<_@)#egZABcmW1^tF2(AqZO}KJ4}`{G6|Lz5*np%Rh8-Ygu8>KHTzkx$ zfw#}!{;{95cjy37SI=OCm* z^_z}p)_0t9dgjXJyFc*VAO7JVd3}BT*`NE_U;EX6@bdD~7JJ4JHdr@9a&hf0$>!Y@ zP9N|Hy|o}W8%&C~V&q;Yn*{>*`>lIbCPdc<^`u;~)|c1OKY z5MnOJq?hgjblngcXYZ}cW~PdQU*so2y-s{9B{wo$N=}V8oKPA81RV6GU*(V-`IAle z;zP_0yZhU}{dKPSy+8VU|HVK4$Nt#A@-P3sKk$1K0u%n;-~Idl?N9&ofB)0}-QWAg zU;6y=>GLOD#urI|V8Hu5NpeBxEnVSGXG6^r?qp_izjyMa4{dm_zsSK_e@YNy-gjo! zd$9Fk%sAN{rw^j|$d8Co-MmM}YXix3&EC0+D5(?nnOAyMg^SrPcCCd-+8MYS3o{e? zeUKMD9TIquRRAoj<9Ara=jSn8SvCFSin#V zdZw=q&!<>dJayfPqhpcX%$P89E)?}AyKUCwh?ylb(~zjy2Z2a=V`eHPWlPkXd(=#F5~VECgf#O)SBubnBBcv$9<Xk+xAiighBIkhz zdEsSm?%A;+k(FtU%87_=7jHklr`$$%vUc&;ToBgi&@b1Z6tW{{ujEF(%}0D4GZ4B0 zCt!go$sIA4J;WB&Chy}zy3xjhCvZTx^`!-Xh?_*OnzCjFlFST!01su?6|+L~8!9AQ z05wT>m`S3mlbg{!89Su1nS`uf1jl5Jv{_a``qSCkC;eiRBkSny3ClgX*79_7++taj zdPFzHBQQa#m&4vdW|DjZ+r?S&m* z2eOP0fVz^(`99-@?nO34XZN1**;Cim4XvPN=3dmok4eA)vpEzXLL$5~Gu^P|Q<7PT zXukzb64$iv;YfI*$rC{7D@fusK{sGIvo%I7x#Gy38l4>Rc2IfGHP<`4Z1b0+u$!T! zy)nWr<1Ej*UOajkTD;}3QRVUy?W49tpdOyc(`OJ;)ric@HdVkQEY%=~E*q^tX4MI8 zc6ZjEVG(=y5IvmrA#GAM8&eRW^}r)MoR2>KP5+-dtN!Ps-_;}4)(@nyPg-EeZsUTP z>5zAJloZ1_&MBs~sao%BA7lH(MJhfU001BWNkl9hk6j+#s7ubY&WbdI~9=g z7=wwr?{_0^W#lHV&V2L+nEC(Y-_)}W+$z%^UF%h7lx&#?_D*T>5N=^nR@|lBRx~X$ z*>xZn955y&*24jh4Lg-2Ic6jqpv+%^d%DgERFG^|FLAD6vnR86rjIFlav|fkXV~GN z&+omTXDI&v{2Uj5DDXsjsR%elVgF?2n&&c@@Fm@=WI0YS)ehRdJQ|DydHNDS*ohRM zfy%tlSON?Yo!R^Snop-*1-KJt`ov#1s3XZzdf0Xns@f?at-_mL_*IkEMRE$Yvwb-y zA)1e9Ej2;N<(0fcT5*xPB%y8N5*%PA`>q3Nxzn##H&?Q-b6vdO*LA(~k8(n3s0fu~ z&^eLo?$*5h1X|O%+|O3*!tcc0V}*LRb<_%580DFhLzqhxa#Bx#duQhBy|0-uH_3X9 z?N^s)ii)t~u;qbU)N40@V7hq!qVAn_rg!0NoEaD8)ZZ+yUW8%wxeQCd_c$eQ;oqv7Uuu1T?(0|vD<&U=cYR@)G| z|8u_qw%sQl&l+PUX@XOG*Ix@YAxTc)ug`-0yzP!JEz^={1HZqWz`b+NQ+yN7!}{PX zR)RB|gciIHh&2ff$vkt8!f9ieL+qH7f6tk{sdArdb`^|BPU)0zP5WlFgh9@_oFhX8 z`0#kguLKF&bstS6eTPJLV_+s(S}0E>eO|{pSGp{c*=1}`z8boj>)L&XkO?qizqZ#L zY|hbQ*QqGd5iFnP&V9|54LKZ9heqb(LW*STlJt2hw2EdhI-A5DT4(m6uCc`>LeFLZ zrdR|p*%B9IoHJcLtL(`ONf%|943;O0C19psu5o@JlJd#3AnHVAS||*<9tl{?Y&q&K zQ7mY$IKb$7v+>~TApN>tb~ljw)ZxiluHCdBE6ocDqUP(pUgl2CJK0WjkX+>r1%#Ts zb6^BsY!10>2>UEyART;Wf5-3m?n@2&Sp18U>dfR`3<51oQEBpcEJS1bx=2D|IdQs> zIOlRENy0RVUNj}!V!j=>pgCTgtuizStGE!X0N(dr*Oi3LjOQueN3Kx1nJy=4+jLTb z^Ta$P8Am^gn4r`B3UWrv8KXu999TnT(;lFaZt+dlQ&T;5Pq{neDfE53dTdE562K#E zAUR7$nTjBeAG|4a5xaU~=CM7Ue&*UNtyji8mpwiLOh|HFSHE5bI0YI=~ zVUjdb0TMjIxI9Kbc(w<`xM^`Lv&|4Tc~C z)HPwy1ZNksdpS+-wNDL9O!j@he*drYZ~r_0?jQQ6e*EjN-XAgsZQyS>5DNuNXW4DR zo%?;^NMW*fa7frW7*s-=CRI9nDgbN7;8mG)1|<1L;71GTBJ+cvO>-Pevc1(lEITXe zRbOosb57{DRF3ZZCL!7P-C>jaJxC-(?m17{w|ZvpY_drvn-sfA_P{0yTk+_Qc%Gii z)PmKFagclOtD~pxN=YNTC$m3lQW~fLw?Ih0EYJV*MB?tgTvrweKJf?yDJM+dnz`=# zo`tea`u^&)X6!sOSKTt&{N9P=FefaQQjU1p#2Ay+vCnC$(U1Ro+3PU*%n*8+y%??) zHYtMBm5^qmt1Tohpj19%4qZiL)jRfLauGRRS2plI&W0q{Ov;#NhqE$Q@-i>4pFb`6 zyMOcV{OAAafA;79i~sVE{ImbepZKYN?VtM>{`t?}e*K|;{CEG^f9ucwsXzT^{->Y$ zng8a$`t$$CU;FEWpFi>92$!=2U*w&F0(I<4Il;7o?|-MjsG->G>RASt?y^xgC`mZE z_;@o7hctDnl7&988YSPOI-QelU)xCLYWIwdx2ymrk-tors zadRk+bdS~H8Y;kSrT=3&*P#<@wx0YT5GqSoM48jteyUJ20NZ^()*+!sd4KLsB%yb0oc&! z!N))6+YrtNGr8~U<>lVhK7}%v8t_crub9Y5{0f>o4qyDPNj7_-y4f5+c2<&j3iA$e zXf)?PC>9z40qUa7Nt-5wBm^g21wbWu_lay<-D;2Hd?@IJ#1NB}wQPjY(<|!U1jPX^!i4xEv?n z_c{Z)?|ka&EJkzRc-9}?yv2OM8v(K7oQzX{9>qNoX?kn|hq-1XxzCEAyz5clp;k!( z<3Kt(B54^vXKh7dKR6@QE!@z!Zu&#C@0^|jWi;u?)>>p#_{0)>D&@z~gwpBU zISiH>gtjvds2r26H3-x{$xM4<3vMe~%0HD>PU9lrQi4hVl8u~}*LKV!etA;>*Y8wEjaY4Aod$(H%J>ydCPl=^K8UQmSyL+Nk#H6npzo^fQI7gAfKFL{B zqX9B=NzM!hJ76NaOY5uL>V1^0kIrbC-OQykwX@Af6qlBlWN3o9Op)Y7=zL1xe9QQS z=j=_W9gaO{G(`+cyt!cd1aHnylAO)ufn2SwHJe})5BHp6LVXVokSvJ_#5U=9Kj)gfvq2gcu;~K4qqDbo6Y@A@DPRlg*q90!bLX#_1jW zn!G1W2CbB}Gl6F>Lc}hI9U-Ti;68Pch>wz;zNl(Ej^xAa>H93X>*B|@kjElVk^ms9 zlP(3$usw=ph4O=`XX@u9a&Jb$3$qQ5vB@#X%oU|p2TZcraW+a1&Sq~Y`Ktr6Tdqp$ zsGhwntK*g3cZ-2wMN%-kpqOOZz(mKpk4R*-J_P79+dAu})Kpj+(kIAfLGSx+dRj8m z*-VnM^lmLG$1yc{$2|5bkEmLQ0<>dmIxJGsF+Y9I-BV;M^YDOT8Zf=53AH{Um zDx}lpk#j8C0ogF_?v(9AKa{(Dq(srdv-RZwLjAUgvQsfF!9C0nl3Oix?cB5JFqNHWl}LaOke$ znFmUT8M+`o#w9k0btvMSHw73gVo&Dzsa2q#Ze^jYm_WqJ+;zmvOtO0?i<7Q&0?lRj z*%t%RyvT`{aVK^{Uj%iBc^~Y-Vqk( zxkEbXK~i-#_gs?6*%QB(mI2U78rp2n=QwVO%9)F#Y%ROR`PIY1)NGrl%%q?Wg?xCx z1IJLi?@ze(rj+a^itrRj*IX8s7`-+Iu~obbtp<^~Vkvpe0fz_VUvsG?4iER$>wxl)TA zniFt1_rtKe2g4_E&k6J0&IGZwZIuK^ zyHvqL&ySLs!-EjN5u46JGE-K91IP-xS?91V;Ebh673a$1u5Odv!5-=%h>3*9Y9C8G z#bqEKoep$#GN$p@aXiPj+fO!JmSCC@kq!icNd}ooo`h6w0LgHcsFYYQfsrvK2-!Na zcIZFnRcu&kl1=IfLaE$Qj=Y6aXZ;uiCx}ArBadxo=6QK!PSV(cdP4`F{Y`56N$qxy zN2n#qF??VcHO~bqm2r<=OYj5|X;I6>^~hd<1HwfF$GO zm>S|CCHumnO4xhU;WmvfA4#PNwQC!8`R*v!6=)?DM3PdGGRv$=TD#B-adWn?e*>L^;@6bUf*86{rdX$_WG^YPj8=I z-deP?-HrtOqrSTJb?XE{sC(lm}!N^00SuM&FF{aKV|g-s&*uBM8*ooy~`|dca1B3pkt0eAN;9*<6r;7|MVaE2jBaRzx7MM z^!@km&&*6neKgF34_g*>H*>fcd(*kWx(BEuZ4^9iQ4%pSy@gU3hSVoQuf9*Rb6u@U z=}4QiGIJ8FpSxfw`0Lt!)37VSQ)2w1?l$6mi_v`eG$7^OC$gTSNIK^R*PDoCOMXGJ zqeHq?^$HsjJHWNvl3{?bnYadRO`?Ip3HO;~Kc<^j7g46>ow{qJQpjW5D^7{yn8%%Q?K)c4R?!I6wQl8Fo2 z5o|19o8!|`g=3~(ujwems2sn3Oo$G>3<@+kU+M$97Zu{iBBue$p^m_V z{Yl;wBE(!wV&t zOf9A;GER!~L_*zA!6oZ(P{oi0%&~cr+7zEuQ*!`kiJVfT=-}ZJ!!`4MGT$sRU11!V zb~7!*oc8H&=H%~?ml47&z z?WJBzdrD8-DJM<{2|xy?ejn=*AIqn)T;pCi8bhW$OyjGv8B0W}!B)&DyWOZd>B=HLQyeb}mamK!n|JIMAF%42jZ^WL2Z01253FS4g> zTNag{_=U6mxPLFg$TbN*W|H z)d4ALnRa%$X`O&YdY#lct4Xr#(f$k~J|o*f;Kd<@@?3RBqM^@YyO&9FhyWmC#F#}c zXY&ru%qbi=n~Y<(-(3rHY`2!&R0nTcQ`GRLDLY#McD#n6XElD^Ar}mXGB#7b=+!c) zqc8^tz)KkWE>WmaXe4;Pl3`obh`x?w8<3b^wy{3;;=mx~d6U(zBI|e2>VWQzFRUfl>Xf@Csw;?N$iG3hs<{C0h zV%LSxMA2Fl*OL~jn`F%3cDV;?=+I24blq;^p7A6sbw>Wu@h_RhADoY`s1M$E& z$x{dt3Vf7^C{7?jMv;t&h!9bH3AVYT_)_Q8sl8@zJhU;_KIUI_)?RD==NzN=)?06* zk1=oOJD*yv7p?Arxb;j}vK(q5&!i$)!aAzG+D7r574PTPzf3(I|DNtMmKAV1!Cg^pXNojvTX8=#X?if!NV4P|b@a*yUV$n@xOD zhQ>`M+hNSOUdRN-RPEap?HRAhrYDpqngl`Gsd*&&H>bLYmKX#+Veo>3Mq3D=y9hQ*yY9(vTdD)ts7 zwG*0$B2$?i@;tJLOXn%4uAJE>CU}-UU~2Zc+V5LI2PJtX`;c>VuaMZnFI3A1C=WZ+ zE}P@EwGbw0d1Zn5FG39aN1?L7N!u&4HJvdNrg10HkP$$cVo zHr4!UnVP4hTlOZK4ViIw18TBKmWbn!Tjk@m8qk}@3NvrGj@s-iQV;shIF|{#OF13k z(n~F-9vrBf&u8RB`T%zdRnMMhe|nc7VP-Z z9c^Uf-lK;l_Svhl5R%+`-}i2f2>ZI+A-ul6cH*}8Zj#NpN+5i=KWy&1339KX+`W+a zb+b#jUvG!7_x-x;z2_ww_r7x96ZXDy=YG9O$&w_%o$Z*Cg!(&fa__2bW*mxg6XH|= z#q4!n=BQ0RKwu~tp;WQE$PjKp)kS?D-^s6>(`tUj6e1pc(nnT%9Rv!w^n zdx~wA3n6s_qsjpE^$gx%Wx;<`*m;F&FkK; zxnKKW(jk*h-m<&g8_THV-npSyx8rfJY5*_tU9ZnSdH4DCoqzqm{L8=hpZ%Tx^gsSj z{)@l!cmLJ@{4eM7lXow_^uxdQxBu?n{?Grb|MHiA{nuY#_%~nwt zy1vtttTH=}O3BR3IM*nsvW(i)b9(CPx_Vg_bnw-*<_WMmWzor<97x-Wbd-N$ih8b| zP7)f54#YMrA=__&?vN+kY$I2pB$Qa2*aovahK!!WK=YJ~@tS*ggVQQ}W=`Fhkfc@p zSOYpv5Jc`i^9iST2AlP#{y7t>viPyHMo#ax9E(8cm<0*<_KgXXj3u#VGFDFYkWieB;RIxMe4!{^cntcBoZ9Wb}B9bnlzyo z!cxEil5l8{L~7ZIIO1lpYlrC<=}iEsIot`E$rax81hs!kuLBMb(Zbfjt?pHI1$7 zI-zp*4Aq?JdT^Rn8s_;~SgWgnkVaM=Yt7mwK#lSsIU<~l{s`BWTx!E1qjOv`1XHdd z&-!MbxHN`^>;vhu(^5j%x}jAlfs$D%L=H!xNh`rO_;$OF+%5CilfAbXz=_lo zt|Zw6auNsWQwIc`u?gSY*qpkN<6J8pcY9JBh;~2}0;k4Ca1vx8&7{K~lI;N%Qpth+ zqwci=JeGG^;Q-r>cmcv=yZ`_o07*naRG0wih9qWsd5^Ic(2qo~xt_*rOYji&?lb|b z1BJ%!(*urVA7mmX*v*Y#tspYPYj(>IoMVgEHJh6}fyw4R*YIr?+ypbaNlO&BXcD@P z6P}4J&u8WUAq{YC^wQle<#Ti=V0Qy__P!IQQFBTUVrU?Gfv?|M-ypD@@0ZNg#5{=8 zai`YV^|DeZD9yn!w534Q&@4~Ov~0%@*?vns+csJq@IpJSDT|b33;43}aO`Qphd(IouUi13PXj#4YtDUV9-smU0MZh*o)|nZ1A9pt|>Qae&huISA4 zXNSU3S|t|w%1a>L_iL>^bWm(woHNiOA#ckgr;effpEx~(wNfL6%}&uHIN^}LpL=&s zzd4OKcHRneoo6j1r(WvzJPRG?>4syn#IbxR#-oxXLrz^Sv}#Kig$NwaIPPqKW2GV4 zWenKpKT$41^~5fxrmGO${0cW+mq@ee$csb$r7~q8rO8}VE~HhHyaoGCGv_FFHs>7y z9k4hqr?$_TPw~X${3+++X%0SbId$)bCj=ykf#gjv5zjn23CUuB1P=^jE3rGfEiVQ1 z>U9-Qk7-;0XR^tLD3k5qU5aJ!jjkBx_J_DyCx=dk=ID;_Kr+$ zc9VTp=MbiPLheoD?A!VYNse?&aqDrp?*Lu58Izb`ar1$?ffurOFUEh{ZquQ|M>oz> zF}H{+I#h)M1k%8rlBNy>Xg~vSQUil3_6YK=lhubJd-5W2%| za;UJ>C`#nwZfajU@s zo;>3tzysWQwEQ+?0el()Ax|cu_H3yp*$P#68a;FGB%35bWJ|wfYrviocuN>~eZ8|B zG+U?x=P4I;pVGW8-`UO4O1+@77HaMUAYm#CJP}k9^}4PQN2}*4XSkdT)$Y%n zlgD*?gd}%%ce4pPg?B!n<`PRZ2{cT1BT{zsUR z;P7&tv(c(E&dvna$^BaVvBjhbAlb#ET)<%pY+iSIg{vYAY~CgjrI~FdXrD=<#GRLy zBo$$5q6|9jrsW7hf=r@Rf4Jr>B$klQru{YUBp`M?KY_Cabz1M8 zaUAKt-JcVz!W*9gHGI_e*s~bT%EubgUZjAv9cXnlPaKoB@ADi=v%71u>o7Cdo}1T` zt*R$%_dogu$h|j^Z1#Aa4QeQ4*k5Mv-n!TD)4YZ^JrPj%UUawfMDC@s_>>K8zejRH zki_jc(xGa)PFp?fIzay%U4X&u8zng_Y@ktl_0{Ag?|Fs|kmM}4r*__WAnh8kAm-HV zJ@8n12n5>o*y#fy4`ri9L|KmUX@u6nNhIYewCv>WS zXa6)PwK6%18mfKfZj2FW{(_bTXDJMU?RK7)roBfGj5x*aU|o>=MK)8#3c#@(Nt%f5 zwONXg#uH$iNRsQCJ$6IDjB{s?B@IzCJZwrDY1&FQR;2X677|bz^_i3tMUGbgjIgx5 z96X<>=ZsbWIJzIJimnJ8WJn0K`L-`=$k1~w)FR_kGBi#Qc}mqJ?cMq~c*2{LW7yWy z`DYqU%>z&YJ~-rZ5{gyTY36x=MvaYHJdb)Er)pX<%0i2nVC)SaK?6B}w&*JU#|)D< ztiw=nAsl~C)U3oM$upEDaV(@X&b}R2)YdePCv$&Q-NTO@h|^nR2Tiosi;6s)+&Bz*H>-~a5N|KYFy%@6B`_8&IF1dY(OJYr9q1oEAi z>vw|Dl1AP5k7k1*!{nKxL@(X|I zFaEV(`lX+IxV6gx*I6=dlwGIX=n%Q-0fBKB4^TQbfja6v4Uvu40K?qLc$T=LR?Bjd zu66(jK}}%na{2R3T1^FUz(-fRh{M2PWmS4nS-Qk~YC~iF07~P6!z8x{ttYQc!#12) zAAgHm{p}p7n>TX+C-;4MrD){27*XQ3&+D4yPF9eWk#NqG`JK7mee%gSKmPH*_#ghq zfB5(R{=fL=|I=rmzW)n9|8LI=-~Yi6e)#Ku=(psI6ae(G%FuQ%9E5xzKrr(zq z$pOwG#;(qwbzWoL|d99d-VyKC?>!F0}^F7GsD4S zbjacyyeg~IoF(jlUdTQPzp>9o7Bk5-jb*2WGsiC%CAppS%%Hqrk|1<+rVbBa*_@o0 z3O6xGXAphMWXaQm!aV@d{#UAVFiZ`P4OPH!;={(uyh`MdQm8ej8CBE8EdeR|!uj40sIA_7~dIh!7|*Aed~0*H%db^Y*|`4BZU?twSKh@bc*7ai`zFOy30D zoq2mc%;5-R`Dz&loDXNFCWBfX~+>y5z) z1GjydL^x%m?L4OplM`D&O-ApWag-co?IOhPDl(+EQ^Lk+WXkZGzJ#N9g%!pLsd{fx zdK8d8LICQVI!nx@)2qE(ONWp*H_nX4;9S;+F$oDlbd>BtSJfC#A=a)Sy%rn|eOp0v z;!dyd(DTb6(7;dxSQ?Zv{+Oyr0cMo zbzG7*ZaA$KPjIg52-XjN*UA(5kf~|Ir8KVS8eJY3L6bIRf)fv^Nr*^2Dm5T@UeQ7aM1TNi$jbn_fPFl}G=R}PcM=w+hX9b~ML&YN z!PsCy>8H+e$2^0C#rGPsHv`NZF~pXV8UQkuoFmrYrQ%>ON{Fv7Wh`3C`PB!tP z7s1RpyO)M^9Yb0)Rsq_to$*Y@Y?8R7gaIMQY+fBI?=Mr7#t{;B`=B-z28rA|p6X=O zCK|A>qe+r^p|l+(yGla(4up2D=FIOBaD~l1FS4&AOtmHoJrBy`ThdGvG}*nT&BjHT zE6E=kmUjlK@W-%{|7M!O=*R#IumXWuak0*lOKi2Rs4D zu9q=uu3S6_!p_*6mtZdvbjHA#=*(qEo;*Xj7q6BTA%Jjiwt}MVxWMmpbeJgvyS3o= zpW<0lj|yQ+N(Q}h({V3Z4tsZDX4qAqGt1gAR9>-fIJg`tL36^FbYbfBkLkZTKpq3t zKo?h&num$l-V@a6k>ME!)4)q1I7ld6HkKjT15=ca7d?^O;6Jmujhyp~iPJeL2?-}l z09uW^joxL!L=ci)v%HkmsU{z^8W4z7@ruVZ-oPXTHuKh5tI&3kKBoi1Tf(5p9wX#M zOy%qNowp#fV5b0vyX&Gek~}jYXu{7mHm;mb+NyFKfh0Lxseg?kYNNI?9UeOYY`oHZ zseU|>r9DLrSz$&~@E0o*sr`H3oj+2-h~P|m2D8~f9QRqdYJ^IUB!HeJ>~?;k38+2= z)yI!yAnR~=2mwOIIez2YC>(nJ{Y@Pj<;b3Hz(O%X{q2nP0z8X(#~!xXRNF{maAfc6 z@(l84`nGdc?14E;B5>%{o@WWdyLaEc-hc6fKmGBaRKDx+*a1(7C>^HjqUa2 zIOiAu2RDxx5w++45RUXvy7gB$-EbB}j;U(FZRE7EevY*mwgJGBxgm*x&=#tdW2zUD zQwFxRW)66 zQI2*(F~vFRV4hZh5j!OMmRbk+4rz~co|bLjU_~w6{mmrQ66)=vo_>$FwG=UA9(w?p zlzyS820Xhz?R3ciMstAn5bM}qK$&Yu&?Fp|pZ;rY(!-%xKVNK~xTF>iq>*EWCKYiW zcT0Xr^2F;9xRB16)YBACF{NAi7T^3*9*<52-Mf0M;K8S;xBaB}Ov6@p-<%yhKLLW6 z)ZLsxE+~yfQ8?C~Tp)zU0^1G^HvM1yA}vrFz{`%mI95^!bnRp6$k6zCW|pTlqzvfL zON&KI7=R(bC5cb91Dx&vtmr3@)KrOcg`P6EzUXN4@j$}yP?1oD>FH9jwHuv;NcMOa zYHtth@pXi2s&Ac4E|0*fBz-MhjVVuT*)e9bXCQqrd5o*Sd*9#x{`ddNU;V3Dzxw*C zKl+nDDV!mLo!N$luQsIsm1$rv^R)~5VMCV!J1;1hyYv9_IG$UEkQ1}BUh~a|*Y`jB zb3gpi-+blz=0i514P9I7RC_r=5t2A0ufOpR|FO8_!)xyQ^)>sNeSLlXko$hk>%Fh{ z>xcXGHTmg%f4J}0&FhEzr}yjY{rchc_4U4AKiofkeSO`1z3-pC-XHq=>xb99KfK=G zeE8|tKl$p{|JL97cmCaf@0*`K%!rU0nkOY6h}k@t6$n5{$Q958I@b3J-zP$KFBiP~ zgx~z{e&dh+umA1c%b_Kw%-I5mo`O?w?dw`Gxrc_5sUp#!KPhlve*>@@5Wjtv+bE2%o z15XpGn;+2u&S)tLg&pnAZBJb&GED#=FlU^Nh&-@%yzZV)V?yGbDR;M~m=Z7vEvBty zZ2+VL3qS>RH!CJjjeJb# zQBa-E5mBTgBNe*STY6wpN_TlWg$W$|WUfy?x$b*^?_d4SKllg#^>6*Ne@6U;zxZ#x zynOQKe(?QY{u{sYE5G`~5BKZu{@(9={o}9ZYnRcd60Dx>C=+^P~ zsdpaB8iP4q_kb|%t{pWRO$GkU_H>%aNTg|FlSV~YMs-9YmRn~E)^D6V>!cy!@vAlh zFi>$m?Vf@;dWiJEo$NmNoDHHQ9Wh5VbdjZgkxn)g8jQyDggpHduelo8GJac6(+a@1 zql#|9Jk6{CK8LoT`O@AQhr9tCqT`{1;7j|O=!Dmq z0Y%0HkCvusmK;iqbNC*7{CPwE@$G@HWw103>cO-AGUYqOvU|?DPf$2 z?Erd6DR%nNg!YM58KNJdRCW3oi%cKDyC|F3z7m{cXC8Cg+%cxes%;SrscOHC4v%Rx zYb!ZD%5)%kK5xLh6^)EhrIYhTh?#l@!XE4s#c@Lp-Bhw*%(ReC3f}6(yP8N%mE_zH z_ZEt-*;N|MM*tr*6Y}lEE*AC6XhSCb(fLsgOja5jM??Z@QaXCUt4;@f{o98kIxI5={anDv^xwJd~~ftg9oW(z->vx0$o z-{*a?5bKdZN3KShH#6Y~XEVC!`eZTPQKD%kpG}l3b-waY7z@dqh|ggN0LhA;1zCW? zi|T{ogQ$)3>0v5m3M73Z`T_aM8Gq|6NoEx42tZaw-EzCfoh6s^;iMA4xoXhbK`lym zt6n-f<`LLr>PveK^(MWhFfz~E5P|OXF+KPeY&}xYk*6MmNNX@Wd0C@su@Fr*Vii8= zdX(3xY_)Ki#{|O1czRtvt?e!~RtEcPIW*u*wt(YV{S-1SvtedtEbx%lr8XAr;}9(H z0Q?|vtZFPy3&-3O%^Hr8C&#+qvv*4dpmqU1)kX zLy*i2o<&_iIo!`|{e6zA-)aIxhVazxfeQyvp-niMxa`ZtV^ z2kpQaRFADG&L_ePM@XBjrvMP3Ry8=IYCLFT2%8M&wqwtNgO-I_?GVnq;F_09*3WH@mMjq<7D-;hLTS6RnPi^1elq7KU{O} z<>>zDetDf4pZcLwB&$%o729XuDX$bsa(u1jn;IA1>cs+^fc?>t%lN27iEBSVRkf}3 z&xcMq4RY@u1UQ-Ua4bGWgSOmn(9b6z$rknl=2BWx#-{nIf-yE9;&fatof06;srr9< z_4oyZR9S$!KtIQ%=atr4G&3d$H!k$lBO!bF->y_XPD^z^T5~kp8!g+85Lrn67%NF0bQZ9rZWcc97*2L z`~UBMjylFOw#SDS8bac!+?#k4tkBX%%{+Bpx7bi7c0zDEI)@8%y#{Sr@61dq7&@okjr&nd;aAFF9 zVIteA@d#j-367j;Bhc>%z>X&ExLbPh9l&)BK|`5e=cR)p0L40Hq2p9)WILQeoOuE1 zb;>7C8{EQ(U-COCGIJz=THzS7P#$5BvUSf9{7r`j5VWpT3fiOq;qliL5d)LbT5&+T9=g z>|glu```aJfAq)S`SjDzKmYQx_wT>^#pj=W{`se0zW?<7=bwJ@#b@uo`1FhS-~Ig4 z?|%OIXPldGKmGFkXJ3Bt*_ZD> z|MH73zWnmD_wT>>@_S!=?`Qtp&;Q)7{pd%(^27h|Cm#%E$(n**bR8XJ37qE_1;})L zVecf;*d@@MNMbgY=MddMxY&2^{LO#z8-Mh_|G~Q#Knas<;V#h{ahsY@vc^v5Ats#n z!^OLl$w@lwkej!+GDwyzxg)XzVK?`8-+lVor=MNUAO6AbfBn^0n%QJ;ONqXx9Fld& zZgzVKf4^q$dY!iSww)5dp_AJ+>mH>d206>eIOR=K%i=DT`p^w>60ifntSwNmIU%i` zoOtPNf+x|PnPAAVsVWDW;ISp}mLkUIUJaPH10l4V=f4w=cJyqT z+TZ;%1|33pRl5E07s4iFPumWXERSEUt9u%tNn6p}X)6G1`hjHwUTQPbmQ^)=JNg)B z<~uK+4E`T~@CX0&AN@D~{eSyU^WpyaU-(PkdH0GyGcq@#U@^;7u=uPH}L93IcIWv z71Twh268K&m71`v}wIXS13u!&B(+J)+~RFOs?-{N6^w?BB>eoXQ$ zn|eYBgqhyJfT-7LsAf7G3SEU0IC**Oo{)6mqD3R|5(dbm)%qwjuN$LwI8a`im{lju z0lW$?p1LJ9h)&7nBHmKk6#MQUl+j^#kXb4zCD^(3Kh8?D^%d|$OY0+GYI79MOl*R% z)B8D2ofnWN5F?JTWRbB{8bcmD%|x#tE;XToeC_UF1EPdDp}OG^_nFEBk}EV9lyN+( zZ1pQr4rD>ReS#)XU;#dM)`&_n;i>EZ3T)FT&{VYT2QoNx@+s|A>zJd6$4{Z0j`nt8 zAE?6~OaK5N07*naR0QQgoSX>Q5L*jl@Z3Ds`VW(BwcL-*3uu}NV#tX<;?xR(u9}Sl zJw4ogp4KF*JkPn%CDvA?H{o@+M9`yfGbEb9z=y4RF13+V4 z5EKeRlWUaN`W7b4bKmCW<3G7-v8cTyD~_k8@s^~i%W~w;`m6@**@bELDdc>KGRtTt zf{%`A2N00wIoR9(Od}bG z5RLAl)4nf=w@jE-bK)XKf-^6NQJbf$m2k}omo#A=l3T|N;<(FPrVsXaYVKQz5$f6T z#jL~Vxf;MRIl4CoA<(x_w9*E>O47mSa~4HsfjZoi>Ic9wNlnI z%}07FubI-dhR04Z5m?yla$OrchddMe!~7S_oJk6dMv@At5^fWdCBk^UVN#2$W8&^o z?FpvBz)aW21!x}|amEDAk`s`#6M$)+qH}ti7b1o%oij`v&eNeJ;CZObtEqj8Nm`#> z4Q87!P>oV!`}TR`y9+f->@$D}t!j)7{cas5a)o5B=FS;pfV+y^`{ApclrVdZH zstVb_)e9yBE@qC>sI$8>7l#G*sdeL(-ElVCx2>-?mo33EX7hrgVVs-9R`zNQGHBtO zEE&s`wa&)sd5-<7B^HhQSRDXJvjn8RYF5Ds8OVJ*?KIaAt83@cqyh;LM&eTusI4d{ zUD`E@oYVATh6>++SJtpYaDwSGZxDp^Y-QrZng$^PU7= zd?qtq8p_uoA(@HE_{lK1)U;s#8JXb>CQ;}B{sbxE>be9Ws~0Fs$MM=OmlsGzn_HY3 zwf3`EhA!=)?PL$aU$S>tK_( zD`Cp8^0_*LY4)3*#MxN+8u$+_R;(nXvrNi1ib0z_F*%7padDnNB3TKU%R&tx8P5^chb{TAEHmqE-_^=$Kfi+$vP@vACMFfN#Q8 z&mGgT`gDj+7EOgrNsyM%J+hXLvmt3a2DXV0-K+EW05(S|y!LiSl1yJ2nm(eTqeINI zo>Ll*z@=ohw>$Jaf)6D!VVZ%=#Sp9i(*?*=)dMsKly~pmz4X@&3fSm{hG(~rqBXl* zvdMvCpM60ld(fgypD2I;e#`)~aZUx&cAIQzk(6kr#bDdXfvJJnWF(MNZgh$k58?W-EywxKO z+0Z5rHik$DGza#fVM)0m9ktmqfdlC=V;2U#m6p_;G*HPhbE0*?oWN!;uO)1*xsK>K zdM?+^J|Z}pY>tsn{3nk+V&u-wi8@|a5<@I6j;C+v#cbkH1(fun9!Z#y6hH3X4LNgw zg#cr+R=}SpY@7v!u9DB(_jEqdJ*Cwm{@nto?{kePI`Be&v_l2#~gq_NZ(!Riie3C>4;@L3M0gCZ((8xoFDg z`>s4~i9NORWW1iZw;EEOc_=0Fh?j-)WC3ln8FwxOm@M+`+2li zb1r0Naz8h77)W(Sg}v|0TypQmx!7=xviGe6S7Z5P+Zp}(=P@`rR+IGiPAG`yg+QRN z)AzCidfz($m_TR0g++U0!;x*^F*x37wPfxv?(U2rsmN?sV@}1EBq5nO0u=~3|7_-^ zcbQ!G>jz-QzD|3~TezFxeQ{2PBo%@X&d=!+M;CDSyya`#DR=42n-iPN*5HEEPhZTF zG=C4o_I>P?&Q;7B=Ih;G{k0$c+OPd+UvGepVy>kF2RhIz2^}Wlm2~y*^jib+F;5L- zGkOgxU;m^}!K&OqQ@FFH&X4bCs|cLg>~^`i^pl0bJOqBdSD9<_1o$X3B3V5w@XW)P zW=mR{C)rpAocRF`XVwldrC*ZLad-RZB&uWGyooo%Zf55Hd8~1jlitNT>`aC=wC^gQLVn;06%lI*>Tw1~@?C5;#E032+K02?^zrT)qH=AR(kG zl8p(e&DyxuH`>d=+sFJ@Wmc{4pL5JHT5s)bZ`<@fHdA$XgO@$uAC}x<@4Sc2dYI{* z&E@Pfjk(`5PS2tNxf5?Wb)_|DbniZCb`xkBC5GJ~bMhF{%t`7ANt>;%R8r@n?tKCU zD81|SG`}Up)&T*@uCGx#yEvHd=!wEP=#@qB3`p4oU~*eaaSUlaEq}U0=CUjbvNdcoFlk^0!(Fj9MbRTm#(q`&@7Loldehw_O~_03XkM^wFnFe)50*qyOf=`mg`{|M7qL z{(t-*{muXI`@iwsKlhja>Tmt$|K)%2pZsV4>EHQp|GU5YKmSi(fBDt7KK<+s2S z)p}@RQN@raLnCh5wYoL!KKq`1+Gcnz8WWNLdT6L_pSIPnnT9`P=|G0) zY_{Ri`kqSf$FrQ5k?b<6**$E5GF=@_;xMqSAV$*mqdPP|ht)v)5R>G&n>Il9@kaBT zW|ugVT`sbaG=R@5k?>r+n|j{4Pk9ZG!2Q67j;74L@7D}*vyFg+9o-1m>HkljpD`cY zdU&2Yj%J~A*)v@AtY@tG#6}!I^swUWu&9R~a&SmefdVuylIZgYR+5~$aO`dFdLV5( zq~tUl6JT`1B&ERtKd5PzBlq(|7}kvPf&xrOiklR-L)`S_A=!B1Jx}u{xZ#+^dK$>< zXx@P~k0?|Er*$~SYUCs?&TH$VbJS2UZ81sWaol)4=U-F#r%jQ2cV_n9vGLV}+=P$haPrNZ2b`w}= zSizF<@GbSUGfC{{B*v6(RZAdyW)4+|gxKdoA9Jic1+IlzbdL zpZCQ!pghC|=YHQgPaHXx%UifNH_Ms&J^od8LDQd`fv|WbS<)3#yI-Oenn*S}M<1t~ z?<_J}Z5(FTDwm5mh15(02VN2qZ)p?2@J{ps z9$=Hx2Q_r6dDAlEgwr)8cN-N^r;jaB(^5`Y?5UP#(H8_}8j-fO0Nj+Y&^@e73a`(3M>c0M>)f?b^;gg0kv zKiS>OD_O>N?of^9ys0LDkmE{nodlJda(s)HIE4#^ESb};dv1BJy^yvU5Yk!a!|2kU zY_6$ucWIdUJYzs=P&=r$3j6f=VAy+|(qjpn6^Wuf%6;$P%xn|=@rx(?N%gGG5A$#A zjl#89G*4Dca?Am+S|bM!9%*$64J-k`*4c|*>U=x^MI4*^_FUgW&U|DfY;FRE*#5y$ z&ccGv5NU|C!5Bbcx~|n_$vnT1qI2|grSr$lT3Kz_pq}owwrGT8A*3O}Tyx*4lMY8} zl`d)Bzt4{eCa1#IF7IS0p-+s|j@|X@5}G>6&RptymKb)fi=KuUz~pIbr%r~Z)(=}6 zfF&xsJC|~NoU=TV)*VA?e(BHYv!Gw{q5&(9(<3D91IyZOSCh)kPeLG+uA3AL#|cTOkM3J~ibStu6EkqP6Ijsex?0DDK9Z7|R@7{~9B0V{JQ2DZ z&87Ed&U*C}N3ZMc^8F4^7b`q5lH6zFADnCU&di)Vw&D|guRfpj;DAGtldz%jCwAU6 zGaSztW8IT_sNSmG>D`lrZ&r5clbh68EZxk_s)ar%Krn#JL{;3X6-YPEB0PlIR zlbmRql+13N3AX_zO^H;)>GChgE>CdM%kb7^j z`!=JFI^OT=7)sObE=jU;U3+&zA?=@KdxB;=wEZ&Hv*%ckvAt|#WkOlkVb<8hU3T>i zfOFPQa?2^yO`h4#$`c~zH08{Ey}e*Qgn@g-gewoRG7ASCk`ViAqt+vzXOn)R?~}_dPQW&*zz~q&ruJifwRiE z56#f2yTsGqoi)LOr=Idd(&a>?oavt=l_LpEHHM-z8j8e+d{W{UaJuIVo#LG@q{|Xb zFO{DJtzVw_odc}J?^nE|>TvLT_Qc)&^saUx&!8w=Q$zO*Yr!jd!wf!=1U8_w2p*eP_ehln(hc2QDfNpzdHh3q02_8cp#B>C)H zpM3T&eQV4={qaw}{Ni(6J$-9-VN(tS&7m#i-m26D0wf@va`C*{t*UJGKr4PJImnLMCKxl>__-S>>x5yGtyN5)7qT`Zh;jLqPM^FuiI=wjII)GN)Q zdq%hc2uYkIH_g2%*Viav+amRt#mz|%sZj)$&Tl`u-afvrpZ~Le{-YoK@DKj}-~a0K zFaOeC{_T%G`}DJa`8)sa@BQw-{qOvH|KBfu_7DHj|MmX<{?SDeoYuTEvMvc+^L8c; z%#zo)23vg2dlQw@Czxv{n+rEQc~a8d?~%JdO;)pK+S%wN_jL`YU5O)$GdM&?$QhUi z4bSM0{9w>Lv&78I+m$;GyTZ&&sNpq<1vy3Wh}$!lqso^AjCPlR@SJb9Thcw{(n&Ui zv@eBxXcL@qbIlW0d3iSB;Un~INSC+HDB3qF;dIMgtQ0t5mv%lcDKz2OkLTMkmSzkm zMb6aVG|H0I+{?@iu@>PgUWP&RZ~-oqlYajNFC4&EH}(#g_bj9JgHIKb z19D(Y7WqJ}Cy_qhF$uv}U%|~`^BzwRVdd&)c+e?xXt#NY2b$1&UB4j6u_j?adym)a zxbrr8dR-`A-NQN<8&-6_PzIi9D#doR15?6JwNIq3oqCI7NT}A$Di0H$MyTpQL!+g& zc37}?Z6H^}BGuQiqDIrJs8|{f727B$SeHE8ad1GKRGu9{8i~yGx-Jo! z!Kv+C7{MOmRf|RUZp0-pW58o?wR`@}3SRc(Gdd#`4WZeF$q`>0)&dje?RZi>Y3XT4 zD{tyS=1x|p9*0M%o-wNvZ&N-86Rw1wHFC%cmE!Dcq3-wPj zWAk~cgTa!@;5@-|IFo>9*+hjL#$;r7rPb^{{%wv8#UJYBPAvi#QRm4D7l|F@h%Z1% zGLGBbpiZ>wJhL>8iiptW=Oj%Ao4A18;ar5#Kr5;1%#`yWX(c63Mztmfg{GT+O#w)k ztai2F>PZ^dh?aYeZJ$fa*6X_Cxqm!ZP@N<-s*^sQ6w1;l2dP4YhS*z=NBIvfq>*=O}U# z%|NMFSmR7D1vq9yfjxQhL<3=T`Tzzvj;(KNp9n@~&Pm_HoO(P|KM^^SIfwy>%;xqw z(ey|kklapov3^v+lX0mcL-RzfsK0veeGx1}BDPgL4x{7dnl4g5TA?-5x=(rDILn5} zz@cxCN(01{Q~^h4y3zv9)l3pH^Wlf9LU2X}Y}}n|VrEqLrqRG$jkfEWV9V6>85_@& zVCoCT3HPCG7%Y)T$*nrFB^#mt)(pYN11PHp0xjCqvq7fEk2cRUQ&p?Z=r;fBO_;em zJ%BT#p7uG-Co`--a%p)L#KC139VfX1kUdwuEXvHj-_8W_#ws}DP=*UxjK_~ATU3-1 z_4xP0NgZ*B8a4ZlQkmTwd95d(DDGCe1&C)5fD>6a9Dh0mw=4XMA~vlOW#-EB3`NoA zABuCgLmdXP*L8&@xq2s%K0wXh?>$%EAxIs!Bn~YaWA43YoSs%`m}JH>IdhG>Q*Y{p zC2jz2B<#)ksT9dzl4E`KoItS(kPw*WVt`jCKvpkID@xk;CP^~byff{GXDVxE2H_Uc zdJ%+^#z1hHu~B?I3;yVwc_?t~I%aJoGXqx5;|2n(i%uxCGo4V=ZrxehK0|_`tKyQV(+M)y zjIE!^=H$uBXSx@F9JTj(X)HmNAk#DZ$^?xHtR6V?)Dn7Gw}h* zlV@gZBb4Q&a-ot9PVE}YfY*pp23@C;=jb&rHmwAsaAz>9oOC$G#-?Q1p1^Y3v4ou= z$%v+DO<$wF*L{Q}*F-Z2(Ec%km%1679}vk71^LE4@@w{(lNpv0Yrt#Xs$G)w7ulg@ zHZ{@mU8pyPxE8w3gCxvcl*b*Xm(S|x16tDM*5p*L>lp@MVQ@NIN~WhZkA6e$_qPrLGqNeO zn)kK}jp^96%PQL`j1pOz&Ch4jz7gTQv4FRwgOopbbe?c` z1k=FlxCgyp1Nju1RbwYU&E#8NIlPnPEKZhh%Vq#=JED8?_BgN|F`Trr+5Pm>Pp)r& z`@3I!@x{fZ4M#=iND^(5XuN3ZkbnbGPd4$?E+9z{?G6B?0+cS&#*3g-=7Hncy%0yM zMY)id4u~{+9S1pPGef0Ob%Nvez_Vj?j)xLvbT*5l=Oi)VN$(JQLhJNGbP(hDs{hVxcz#DDt-6)>>>ud1zVYs&vsDBSb-Gz!;RAS49n@OD#0=k z$Y!R`J37bqfJYwHr*DK!<|;QztTT9$6a2om>kMHYWL}$_6UQ23*x^t*&0A2+v2!Ww zU!Ipoy&~pe`01%6sU|(jL5xu&h3$-@2{PR?VdgrE;Kz2R3Ky`ZtS9Jt#T*G?p3&vd zFGomp9+s}oKcmKlg{>|_I^5*@WF3kpV_&K%*dJF)1wesq2-ectb z-rd~m;=S2*llM(FcXoI0PRJ(reJ9+7X7}#p%JeYqoC?0#lB%r|)Sxkw zBsq+I7%=t0ZpL}v)XPOIS#0NI)xfzZ%>(mEn*0>LN z&#Na!a}SOj>z*OM`Joop6O{%)dym>WPCT!3TpnakH(Z|zBx!RdRixfk zQs;FmPY@(w>|6y%wp0S8cAM-dNI=eV! z*GbcAlpXnK=97=!KL6P-e)OY1{J}r?U;pgSe)*ff`IqMHlYiwm{@j1?dw=8K`s;uF zkN@;f{_#Klzn_)FYJChf&RnT!3iV4{`e)O#DA50j^nj6<->vRFL@v#O6t{G|5J;3| z8!tXpc=X__Sy~f;P61#byz<)_^yRQ)&~yG6fjRZ=^jq~4HQ@9!JYQ8n0H<-9>h=_@ z^Qu5?AmU(?z>#~*gcc8SknHZ~*n0m|_D8OgiaHQV_asS9=U(|`$C5pe$wNrgV0=c{Bp<|I|bL`JRC(TIKhYK>Hf!Q+1M3nc=~1{6{c>J;BVhoxv&gPe6Sd_*|AJC%HpbW}~U^=Sb)a??X89nSZMjpyL` zMtn)0c-+%ZR=XbHbC{HS&sxAi!DCD8UZRLYmXmghHDVa)bp08iiq5$A>Zl=BH{e7% zNo@k28IIV+pffnBn!&U5aE?7+^_?YBJKhw=$~h!EX1QsE_CIwn(0LRX`Vd#LFi(g2 z0q0_ybQ1p5L3FzR**!p4Yj~_^I47y0Mf&*bVP{Q#=}$^gMuav2?P3MaD(cJ!Us*9I zOZ#`g&i;NiX2AlR0vQ|r<4O87afvUyUVp#o>kl5$;se#J^5J!?&-N_PNELDFCt0$- za+cEhh#^={*zc=7_6tPLWy()7(rVP>q00iTFBMbYKgn0r6Y`?C{r%t?Sf@NtAG16o zvLBMhYk#TS3hT!c1d7d$^w{e#WK-KUo7D8#vvmzbkK5K8_> zsmC?d3d!C!k~)U4Ex@DbaOa7~;#ZZbazVrE{6I?q8I;R;UytIUAp zU&xIFZ2&(7n$u9d&@hk|%15$_biT%iq(u6Rq6Cy)&-Vc|r2zqa7DcnzuS>4ULo9+Dus3J$>?d*_JeD zS)<~a#@W9n91Bgl!vCS8ZpOeF{jgeol%7OKdhwwIT4RrJ!eeZ+O7n}+anCYQfwhv= zWW)A-U;Ek~&s@*=*ki%yJDC}ErtOE*a-a-$&Q(=+`yS!g$T=~K^sd)E=+$5zst9fC z&e5oxx}%e`g(n|9dn%Ms1rj*@Vbb6{(Gl|Op1g@rx)W|*=C z+CCj?nm}w_7?q9Aj9JnVb5;?UvrLu3kkM&PzhGtWx&{vQjgp7(&tD)8IR*6aFf|cK zPojUY0?KL>kqBa&l%k7r+A2zCg1f&wdId1uyxKW}TVNG?UGq zm3HY;05hP03%5Hzakzw*SPQ;!P3&a{04*c7Gpy#2aX8V*jGJW{N{=H=SsD_7EM@-9 z0hBtoxrLr=4o)5G!=Jsbt4}!Yc>xMjR^4q`e0k*1AdsYxJC2>#djg{)qH{XTXG?LW z2vb6Z{?2t!N!Rh_gkJlc%jX#q-4QDBj7a%;8m&q}OGl`mz>^j%fKAkp-UD(9P7W@I zc`wC*yiFg40^vxS&c~#TvG};VU06;fR^C7s(%KTzc+;wb`Q~t^)00lyj|)nZLBW}N zr)p?%J>d7A^XxemIzU%Y%~DGD{78@Lqm?AGK$1^C{q*|IcfR}iFTSvx=TR}wyT&t| zmHi6RoU|s(j5O8vAaPD;!8rs#2TO$gMmjha9EWM$l%QU0ZWu8F(i6<&VCq14+N&R9!oun`J|AkyV5=J&NlC7d@b;t>)^|6b@k(zv>!#e3mtFtP;`|DE# z4mVn%OCBGx9(w^oR%ukLj`a&T&OYPP*JE!#&kS()c7d!dew%SDUyff+-``<#PHb2W zslgLYE*?k+^Ni;!0Q;4Qd*mcg8adN(bF4V?y~sex`FsmKr{{d*IW0#}0X+&otUGS% zmJi+P1FS_r#V%nV3){q4Bg>z7c$mk)BAg!0Q5dMgjAAyoOl!u8=0|0poO=u@a@eaR zN;PTP+7UhS&r8aHnL{u*VqtnLI(n$q!9mJ>nF+&28oogy@7v zdh*quy?^_=fByG=|8IZ2^VR#Ez+MXrr`5DK1WZB@XSa)&XNiHunS|`LC(Cwn$|9I0l1s0ngc9JCctcLd(kwYggwY|C6Ff`8(h}cs?(w@?UI|lQ}!lv z4a=~fe#DRd;D7n)pZ?^N2@GwBxlfekajRgG`^^i+MqEgk>nsJ*DA*-YJZgJC?yH9C zbdes+CQGu(XPc$Ju?MwHAF?TZlmp>h%ZHmKWVI_}TEFn!Kx{%~;`AUBg{K14(|wIT;*R99R0I_SO5#_#J72$E zsUx*zx;vnrfPNj7Fr2(25?05f=fvp9);HDRj3A5Qqqzw%qxM<4(CcYot=egF6W+Q0t2fB55n^2h)5k7wo@%`%rxR};q3S@vzN zEhY}kvdkPdD-`$IV!0t#yqKUaBJDcdM3fAkd)b@a=BVAjNsxFL4DB+=#+kMd_TZX)I<8niWK;}W-I74l z;L*!zX6c}X%yam0Uych#v0`nV@*DeF6b&m!V{3F31)LY!%LQVi?eob`|cpO^j4=H0`Lt{MQbe;ew z*;W$A6DZvtj{ek&K8Lfm+y$pt?h~#_hC?!~HII0lmsvE$RsKtbt}NHI9Q@6p?EsR5 zfs>SAAmr+gSUv2j_nu$)p?XNN&z1VJ6P0Cgy*?^*Lax164v#^;d z+$UD8gK3R}MQK>+0}$T-|-E*YO7- z!~l)TuEvf^`+QZ?>cs_w#axMJo^*yWU?(EAN@glbPMgss5g&#{Ql5{YatsMOiOI`= z1c;I(#BAKBx}~WSsPN=!bf(U2NKq6#YZ}GhRpwbp?Y!ooGdh#{J>lfresd)E6{znE z?LgA(maVXPg9dl2d&7)rs{NH$!umE*C!1Seb3+`_iv!28C-@XTg`G)N6DO*?5)uTM zd4uPRbRnp`F8TM~yJ>AI6A8^(sXOQx0$L;YOyP~ZB}8Ft1cRb)ATz|UmxBan-csB) zWEv#Wa;WS>OT&UrN12voJutc#5SMgNOUkip1qbT$E+_DYumRYk=+NKmHNFS20PMxN zv?U;2-`kksf_ZAtK)jAW0xX2gt!P~-K!4&p(r^G@q^hrQ2XMSVlWZz;YMgJ_wem>s zJG=}tZ*-Oz#y8-SPJ9SE*?lkcnR)3ior%viL+(lBG!S-*SJlqE0IaDegqzGj+ zPj~~P$-)58{%}AVdyNYsi*Gsy3;WMPJk_Z3;bC3-&jZ~_inm0sXGbWav|p7JKzxon zy9j$$P{t;}%u9}@135A?R|1pGz9IK!m`S14z+P>_XrxsJNiN^YM-3UANi>bQKGQDO zwQ|LQj(>AIu#VRd6C0%gwZGv_JbFrNQ|&o2glyiO=`;CfJbhr#v4*!^?I&rRPwP#X zVb>wq-pzq=G&4uXOgY@Zm2@vp<(R`4RJZAZ^Ljp{JzA#Q0xRP5!ZH#2NRaEyneJAK zt5*v>xoMnDXNKTj;=IL6?nbcA)f)b%AvIwCKj=_*-FI!lf=2Ru|i74>_o|Ek$#6dmIs)~u6Eo$&| zRs#0e_BnHKjfBUsz_2F7?3O=sU0M&?pTfkhQVn_*qlp|3W@^K1>iiUx{_-*tiQ&@r z|1(!HBQG?9#NOf`g6N_VdJ7M}GEEJ|-l;1|%U^+tPU=r~lX^m&xpcDkX#k2j!jKbm z$Y{YlYm)|LgL?JC2$*({tF3oOuj7_N^h#c*BoZ=b>nc18`#_1nalFU#gmATu=`Q^u z`$*ni+6yJ=E_HQkLvcGpNG6cs`66K%=d8Y-m^kr=hRS;jAt5_kD0o~pVDLIF=zg?H zZ0@rPcDyWfxH3@t9Fv*!_GbbYTM+tem@c_RO0h~Ygjv{$qeC4x$M~&_TCf71;Mn$W z#b!8@g-r^WBM5X_x{#qNEOL2~X6xAcYC9>g!d#KCB<)o0uKzPI@D3PfhCW2(tQTf?Lq;*h=Hrh)xqj{2 z-~IfHFN{^928=c)M)zFDO<-n(g3zA0kYXW13=Z=G>)HK)8`xxKaFFZj&tTND0~>;3 zmb^qT4Al3X0I;JSS&yGclFH}kvt@?lrOzp3-R0SljN_R+RA3_$N1bGOB%Hq=aHvA% zqJma|4~kg>E4Bl^zR$txXsVvnJzIq<9_3s635+q=R4p2o$vjdyZQ3VFMs}vF919OA<5z->HI%WYMsd5xv@`{sz47Tg21$^ucs`J=MKe-pU9kqia6-X#VJjfA431@{>v;i?)g%EOvYAnNAprES@~z!DSXhKlKl|*nZ+&{r_2VD^ldrz~@|vr< z93CVm=LDH>&qdg5kq^zUcTU#MiD*ERP2X)F#&GrrYB40wGhiC0%%HRPIWRZ{w&Lo~ z=TCD?+4{&sHQ|*vrg_6>skC~cg_60hv`v_KPcI8ELIcznNf>*5?kPN+P9u>bJQL7w zLO`or58P1{Zne|7BufP{Fy?JX) z$l@$4EIM&wJ}gd!NPw-MS%k17lb-%3f#l738$Nma=;#0ZXFvRdANw|HuFS&;H*(owqlkiEg;+k#RO<>A6i}rsuCzABXjm zC5(7)PWAvB$QPc3FrigwBxUqW%f?cB5{&a=I~3&(cRmj}+J3`tJ%Ye^C2KPsbNHQ8 zxjOja0|4__(!&L!=cIQiiXv&jfGCh7}UUr|$Sl?6i7z%nA;WoD9_nJO#;sdmK;#mMY^ zmb4P;mxZC>$aP)m#e#&ylgUcxnl zJ+*fHKAJO^GihB&HKF;|1Ori8fM|Q(CY7Tp# zIOpX3kPomJN*WTdeHy+}kmY}+g<%p(hh?35dF(pP%icQT8*ZJNuhpUnWTvQ#lyiFW zL$KS6DHR6DE;DV9s614`ed1pqRjnyZ6&);=#drUDmHnoV8%4 zhXod2ur&=LkDkxqgPsOY77P z)4*abSZI*I%-pUxvSmC^2tzYfk_&L^q`F&Sg8*4;xLTgu5zg1QN`_6R=`7QEu0BSO zaCiA|dSP?N1WLT8u054xwD%@-R;$9HIQ8yXMO4jmRtKe=z_{Qhp^B|iSD zcdeq&pWWVF-RuB~61omx$FH8#ChcW7RPx)~fPv8%M7o38fUVn5n*gRI3){|)rYI=I zCr49&aoPx3^%~FR3SBzO!kO^}@SF4TMk&eYHBZ5+-_2pP>F!z8lMqC+twa%>>lJ%R z?K9|D^3#T})}EA9{i~Ec`fX-|jB_O`(sM}?YB}5fXZOHJgJ|;1gY%@#Cafgose172 ze&z51-XspZ65Vl)uLO7;D0KkMQk=7W*dL-j;&twOo#{oocDZ0GkR-eq0oG?>+6@3h z`3cM%~vhM#+e19EjdrhR201GFby#OIvju~@K)ySl2G8Rz`g+X$&iLC$>kgc9W37yJKn z^>@9yeaU(jc3m}B?|#9(4cZtSCHo|@BCt(32%I4>L{>n2kR)dj-y|ndK7(@-i9<*n z5g|(;MJR{X=0&nM7)*HC&hBn^ucz)D+%tzx5@+Y}3jD(^Ng!^}6z4GL5_uLbI!Y<5-TckUZowkL-%nkl7bIr{k z%l4IL4aOv6E?ia54YTMx-$b7oJ^tUN6<{4Y~7ndX(u-idu z?w>fZCg`pZ&JKg)_s<6|>&^3U{#RfczLlY(1@3p?m0WC)n%?ThI1j>W5tvd+l6i*Y z%V8knEE!~aN@&Q;Qy=ZXr13$NY0rl7D!4Pe(w7M#21N{vBo{|%KhsMh(R2Ynn@|an zmPGBcX`-;bUy+)`Q#HkJ>)vMQ)k3`)QgHE zA&zX4^IJfSTQT$61y%RWqm7PcVWoBtcHuNc$6*SdgpMbI5@r}ppDhf$TR_z5#IUk} zGcgip>YokQ@j^byS+$y0!sDuR&>4r9*d!YWNfPG514yD%pOH!Ed6T+nH|lBUN4f9& zOh0XglGyqC#Eaj;$d`ZzpYwwT9SSDtrj`&$zDMB#XMhZY`Q+&n62&Ww)6AxpCge5( z0^{@J2>_>{+&ZKQ=(pY@&kxds~yV?i~^xDc!XHfUq+TB-QZCE&$Vg_ELTr zg@z%VEinA_r$2lC@Rxt-SAYKJO-S|boNbx0w0NKcEwjg)EqNm|hR8r3uVhq1s!TSU zWvnb<1WEg8MWxi!@1&0j+M3`>E@&&vmj?heTx-C7LvV~tU2h;M`aoK>rs%dKX@P##_l$4*t|A$iT@8$^ZSt2uO!DnshcluCf& zCh)qLomAg}RlQA;LPo)*eduKO5RgDFhTLTE4M$J?_AG?6Q`PNr@-)=el@E1u7e>!W z@D&>}-^|CuGS^dBgtcaOW`RdG=U^iUhRid)p>%s$lay=>sUqmvQ5-_fe-I!#qZXio zNX5}0*%?`zDY;UZ@R9R59T*e_KGfTMyn0I>ImCf8&4sZ~ivl ze*4*OH`ou3**Eg+;jlw&Tq%$>bn>B}PoKgYL?)Wgg6zWBO^+*GoMm>sk8um?3I>)T zQ$@l}LOyKEyGBc#{h$+M;c1bL7J>E|>wd)1jrxh^o#9XbX%YTRe)^67=;O9U4gJ1mO z=QI4hzxVh4@o#>kWC5}rjKF{}wl3Fui?fBq7(U9p{d;I-jkkVK{sdeWr`uka5f{0)dpEP==5YqPlU64a*uh z^{~24&^+P1rkj2e-K`I)WMBUQXMM+0F9BBU>LIeN+AOJ~3K~&zSSOSZS z?IxO5wh~Oz%IyZ2R`_Guo$aIDF*6mfqE{k#bjWu^oO!0xz%~u4PuQirk#)#Uf|pQw z)8KuE-5PR8lFTz^OIP_abfU=0WPE5ijEl4Di{aSZ#VDVBd=>)ZlLex(d4xF$ zQy*9JnStSx3lveRvs0+X6t@z%Q*{R~@IpH8fFCPmb%C7Sdn4#08$5ye`Uv0@#UU6d zzc)HUBprSxCOFk3P{gf;IoQuCmu^tb`qKZ}62=J&q`ig18F5skyBz(p%vV>fDn&SH@TY0c!~S12TB8QXH*bJK0aL5 zpaVK1ALXV|fto;wsrDk)FKW#JaEmnfE^>(`u?(7kGhLLI>iQNo_L4`~Bx(I1V5eU4 zZYhwoA|u(EcU9!=5P(LF&&>iL$TZkekZU@_I}ylBWT}B9`|+L6-m(|UoB*RR6Ot*M zIS#18P)J9>1Z49a|bWJtfoYt#|ddFwFTj zslVGbdU*^v&mrQPO&)1@*7jP-n`$r^E5>_6!1I1DQtr{~HjBz={ht0ToqknqWjERs zu67mK|F0;V25YZY+o_W4yZ$hM#d|>f$Ci~%> z*Vdnq(cOHrOW1fr=WIfGi(JcB`D123N;!RvEYFq?9dE<;|8B z&R7?9rgctY(mfAXN++8M)LfLL3lQZsGLK>7<;_W$`ef{TdD4O_N+S| z4(zBt6&vktr3FDN+c*9(A`+aG!^2{Ll{y4Gz`9MWW--H%; zK;C%&4mq5$tpz$kWuOKJT2Z8kc+J9n_#9?mVTMrf8ZUO-w%DXx`Tig{JDR#Hv4y>9c=eIods4ewb->m?dGM)mdoT%)HQ(n+V;WJ8=2iEb50gw_l>K9LvLUfo`o3M3hL4TcG3w|3F6W}fH#Y32)nq0q)5 zYxFUcDS*xBBKUxTDV~}7vpTa|w>cm};Qqc>^H(Bb7|f#SRW1l+o>;u{Da=`)86vdP z4{yHlTtpp3IyvLH9)*iP;_W$pUm>F;^k7~ySH8UdmP7+dFbN25N!L;QmSt^04>z+za&%Vg2It-kLPRp$CFRvNfsmkt8b z5w)HvR6n%GnvFBi?{L%kwb|7AjU?aA1dE-)dY#{`l7{@yL`$M@aKJzYw9g@xr)|humaz~R%ZcgQU^5CWH zo+QW(r@X#hhj%(<0Ej?$zb*Zo^L23`Qm3y%wr+Z6H-H0ekDpwaOwY7woupaz^sc0@ z&{dX@i%PB`&;mFh$Y85LNDnF?#qCE|T)ecC=cyN^9$QmZ+I9>+z2R5cfRZ%IXZH_( z`OCld*ZodnX=&{W)(cPR#4ulzbJtD0g~Mbo4R z5>KLk{JYOz_@n>HzxFr(t?%n^zwhP*5nB+v06+>SNl79?Y>y1q+X8gTyLB*l7nd$lZ8^SSJ$WI=B z_LKa_f9L=CXaDf;|3T+io@N~Lzd=uZ7%x>MtFwBlq&&&+9|JBd65SBI56DQM&GZ~(2ESU!Z9Wt%J)y%+8zRgd*J^%H8 z`49f~|Lx!Z&;HXt`JevrAOGcl_Mgqnzw|Hv%m3=X_BZ~!|KY#?yZ`B*>?c2YzCpCy zJ;Adl&den+!Hk-VU#0v^aLP`RDK>|%0@;uZP1`Zf&5>S;A&v>PPz3m5~^_pq6W1M`VnpakZwt-NbUbsy@b;% zSN2N*!Q;`nv78G#Q0K>ui_q6HjcY>h+0|}H(MYawt!2Zc@_T+1@tAUCCIxn&Ze za|&Ye!3x_R?pP8u`83*9A+Wp8K*{}-;8nkZ6`B4pQ+)w==1CUG7N2H5Q7kc-`gTW) zW7pe25$Oa(xXj+eYU28P35B!>F*zo#PW8c9Bx%45Xf69lY;8!|06ecHTQmtByc3WZnAYw*$_j!um zAt5l%Jj}3p-Z(8P!=zdk*ksHAGsI-)86er2InJ5R(61nP0XryuZyn=)1(U6DPCe@m zY<^d8>kn3w!0B56UMRHYDCE94zwy;C0_O|$3IJ*StgW{XAC7Zg1vtAEX30Ev_pI%l z^qo}rRxZ5oR?wj-DFk!-#C)NKzPr&*0%e32VEg4JBp2a;Jr9k_)#s{dvc5?A8lwc- z##Of7own(i@p&9W^}eswC%k2RL*RN+>VIZxYw>kV#r9ub$m!+}E51 zCh7I@1@7$u#B}UbRr)a%;^tqazFM%md)W(n`C zMA#&>ewK^ild=N#{|)CY(dQNKOCFX+l`@RUJ`5$ScX5fnP)lTa}Y7c4clWgcNa?c~%HP(&`;1}K*D*ROItkvn6*-sh+VaU%e1)^Z+ihR% z&S_|n&LWBglyB#Ty{{jIj@51%>AFw-57HS|ReT0G7VW+yCD;+KnC@@_%-rN|=D9IS zWw?PH0v|dGt(VelU*5b_XBaBkmo=v zq`{lp8lLAVWu2lg>_JjSx)h$|DM8_R;K>MCG1fR7oZ;5nuq&A%I!}NZkp^0l$kb`T znt}T5egeFgOw?CvgMhK!R6uROy-CY+;?Z8BhDEdo0U$2Edhx!JjuKmbQR}~7(&@Bg z^Zv8R?mhoynnHlQ$n|{l*sRgfYc&c247_8#Wwu{zc~p7w*)WevJhVYaLmXeSfBg(i z=e9F!=3UdKYNocF9_TGiFwgaKMEJ^($gDr?uSf#+svz7`SQ!Y0bIh;;x-9P*y)x1EEGPFBpxvqF0surGr?2q z<@1zVNY3HwB|j7ds4t2Q4NfF;VdDGc{UwgMt5yqTyAf#?N;0sK#A&5-8-%JiP$Ens zCOh-o$Aqsl1z+P{oTh^Jjs?=N==gJM3>Uu*O!B()`AJYkWW0Jn&E|#>XL8$@14%a= z(2+Z7tvp_M6lZNjoY)3gKBb_hDq1+8syUU4HfDyNQ(nVGC2M9Qt#PGL3V>u zK=ncdPHw75@fQl**w}7d+3~5VLke^Iue$&V{#b@P0UWnC)2Lr2A*xOzq4sM3>iAX9 zKmlOYnIU;mfwaC1m~CXfX;5IC`K}1$#OPRApKv*Y_-(F$%zAn}f-iRwV*q|7gG2w$D zUD6G-K5PF2LXr%lhVFX9cLZpM2FUS%++RYVel&@R<3&CA24F%fWGqWGH}T4oY%t{s z>}iZ=Hw{*(q8R7#c0xF8z2#n?&*v}x#b5q&|J*;H@atdy`Jerx|FIqs_)6*_Nl2j+ zwqGHs;?fVm?$@t%O6oZw$7&J~7>7e`F?#}p2y)m1OXX(U{07wSh z1AY7|p1_c2J})%Eux4I^(p-#YcNl(LdZWPXz|X$%AOD^I;2-_BfBKWV@}UrZ%p)1+ zi2H4Z z=Q>X$P2-!rJ1mja7Z+he7)Twj!i5kBYWhv6h#58Jd9)fiMX$DGLfq)6Odc*w`nO^! zP@13u^QG``3p;iP;(G{C_5wnp@Y+~~(ZE`N;G9K((&Tkg&ktC=J=tqhdwI^2HBJL< z6gpk(RUN|7eur;gs9ZFC_1E?RO%mAPypzlGLmKjhB0s2X1Y5DB%)sN(e4j`{k`Ou( z?onMR2xK+163ePnBN>v~1j1d`97sANB^9?KRr;Si--h`w{^ale?SJpz|L^{rfB3)n zD}UwZzxXG9{>xwfEC2ex@z4JY|I0u5&;N^m@DKj0KX|^G)U(w_%IFfYYYVK-qj6?z zAyGMM-j$JQZhRM2pif_WYSRNeoNup&hzT+z&cv+`>fF*PU|DzRg*QG{~;w9aH!O zaO)eq)l|aQqg^4%%wwhkrGHf$!o{7S zgG_a-0x^--MYr_A+m)||$+qKprfMQw1{mlL_|J82??v>Zp6U4q>6!bkx3bAN!$86f zSbX}?@|HKfMj-lyO~0}s4BA+mZ~dXmY@&WmBF6Yo+G90CG~ubykzE6lK0IpLzjeRcjp;NQvSEy0zU2uTn2^U6f~4R zQ*UKA4>K3l8=i${O!?$AojAeWHJ{e2@#+?90clgR4*IkX!4~9EUcRNFLB^JJ#Z+nH zp%J~%CBE3PvK<579I z#@Q|!wlz4*Bz=7JUZ2T3JzyT94ymCDC1?akB#>$;4kT+ZsR^l00gfiftIHSE2JQl$ zluG*3Qv?N$Ig<`$xfH@64|wsRlH41#?&>ovo8?n+EIxIJaUzLx7!E&7Y?riL>GHxP zmNH5Il+BI`#$y-f>FzEPmx1b4g}`8TFL#i0IixT*9#y@yzvD}YABJHFE6?kk z;Pg0BymP?h2eM3&OUE_gi8e?(di_vCx?~@=kS)emm0BMm#Cjs>@SDgsOJQ$h7<|Ur ztndKNkf~d$S&3{9ens}=Bo)7@>z11*)Hg02z7$@-Yi{Y?XhzA++ZBjPzTg6r&e%BTR)8Hdz07~Khv=m;*pLthh(sP8 zU9)#hu!U#{Jps_05jue+!i*n?%DgFLfSWLfXMU21sS-7X@y_1%=mZFx2|nDtRB6n%f1PxIf(*z1 z^KEOleh1XMoV=RI7*Y@NDWO@|%ga-5TVLoT;ju~`#5{JPhykU^z5RxRHZG*VkdS$MS z)0E`1SuPX^lz?|X=o4E2+CHyRVq?#_ra zkNa8ZjHm(LN7tcrVFK%OgE~`Nd`PV~2l<30WvmFDO4!k8U{ObCoW|=LGtc-H4zbOq z9wenxlDUP2l+sBVYoC4}bT^-#>6q+`Tce6NI+E;y_UKAmN%Eo96MM{`iJxy?{|9pf zCWh}~lHUFd_;CK$yD?!c+vrHLlLlzpjhy=sM87~HOx4%zML0Z<(s?zJpp=ZrHysZtR>jK@qg#rY=~! z03z)tIUk!s+I8sw^(rtZb4 z&u6QZQwOMDZD?`<7o$(2<0hqv4le`egwv(qdgIL2S>(Ml&4$EQ1%BVplc`9WFo7AC zJfv*g0=KgYIs>^dNZlF?<@58*fF`-h@TWih>6t79?k_z5dd)N`oy;W5Ha|tR3?N}q z?P#f|MEWZx$(#tilA2y-v=h%pJ8B`i%^K6)eC@u*D63asXTFrfG*~xrrUK3R zkxsw2a@rV52U<1C_-;|pIN7bg62#(PeJf;;n7oV}^ldN_lFT#vSv8~r?6FKGanzeD zw*P4#fJpyOeDM!S)^?9}@j{!H#54`cCbjKZy3=nBuN{kbT?ux-?2DRutGAJ-IA5tp zhId){?eMcH&>Lz(Hndo4ob+ybJ>1&w=w3w9lOWk~AiE#O837-6H)dwK7a^pCzMJZh z{c^*T7NgR#H!-~$EWqdn?b&CTVxTIilUK0AZA`@mQhvP(GTC0$^p3mjzXL(iel}nW zE9wBr%zJ|@CF^v$y>ELwiqqar@zpCNug?NAvso_Y>qyeS12XTe1&ftKl7?7LzY&Ohz-nwm@LUQm!{*tI^VeS z`XSuzjYdozsfQ3g*W~?60MezyG)msrlm|xI)7Gn>XFj_jX;gV(2=sh&A$q<&Y-Gq3 zKn~E^!eu_+xBhAJ%$zci@=sO+zpryBZlQ1P`_KD5L6fufZiHnkVb!5w5}e7V;)a|* zkR**_z?p`tsaZ?v8pCs2J!4bvoj9fu@)w@CxSB&P1bCA!TANUWIIjX@*u zr2Q{@;7!>m$tJs9hZ*4V0w&3+wj5M-AK0R4>9kT(H%J&px2svZqjf@{;p8SC8n8?H=zJgHv35cKAVaS`FuW`on&{D{p3RlIV7JD*t=eK zm!xT@8kKOe+3z2mcAGck;B-ba)f^A?*$_7%t)5KJz9Z)j7h=sEPJ3*W~Tg69`_W0UH!eVRg0pLw?N zCA)hHz_;fX<4lTYkDtabM>~)Z8o8}oT8ES^ML8bWd;FQi_-&qD%sQpL5P^9neE|1M zbmysi_tE{y+V1|DAt_U;O;fe*2r>@!4#uHw}&6VpE;F zF2M&dGd`^r=b74!#$QF5X}T2eWR}*x%^=q>Z#f6`&k{%_=t6*}PAS?376=WfBPRMI zuRDakalGsRNqqg_v_u)g;FNf$w;nxxb{8+v*Y{Kc$q6QaFbbvplP6z1oV@?$%m2gG zW4rmhRETD4cD=+6ySuCNdvR2a5}3JVDMDH)o|4tC-bdw8RJiQ1sBXDa+) zSf`1A8VQ9YTQrVf=Y#8eCV*GMDssB%%w6nd%a;!T03ZNKL_t&qL}n)JXwS}f4w;7Y zt*4YgNRs!mhQk|L^}cyIG0yC}*BK(@rR%+0pc8C|MDcQ6OzYM9!&@)a0``)@9-g4B zy~k?Tf2bYFl?^wP_isN)n%3G+1Y$h3<~^W7xBMOlvD0NMo{bLYv!CpzF6nqakIlTb zt&(ifNRot`b-Gy=J8yymVS$XWTWT&wXp%-pUo%F&JfjYA&ICGu!q@yUIOjkGe+jaQ zRloBoU@KcE5oWpW-@G$Z82X8@K+Ajj2QZtjMLIE z;)#Ug9ga!1Fu!vbz&-~WIW_yn!F)c3Y{E_dp@@ZAr$>d+8QXo>ocvLM&#tNS_TJi7 zFM2k!?*;i$I|!WT_#qHBIpccqtJQUqZMf%lr=5b8$f{>~ zqoe2S@@D84kUJn7N*$3fkenUcMQH&8w{QXSRul=KKHHl=!G4N!pv?RD(5846g662M8n2uHCOnom zq8<*Ad>>UmpQYLd8_teRTbSAdHXn>B4uNDTr5u+5P(uS>j*^W7kSkFw5ZS!u36SjP z^U)UMzt_W$_kxjhVMrZ$T*923`E0cq?8OCQK5JT$a_cHUyV*cZow-X6vCbxjnWOeL zOJ{8!nA=tfu%91Bc>${AWMAG6GM~??RM2{cZu0qTV0V$3=`$?wGJ6v7Q8t~LoURr2 zYhtB=&F_%wT4!bku61a;{*JqA5RxDA06_INqF8yWuT_ms&!PE6sE55sz<4flAPQk# zmwCq-=h9Xs)+EDB=(Zqm%M0Vs`k9Wgy9t{lDKDtW`OH>a2XoT~vPm|(J73ESuVq7e z7pjw{m7rF%=Xv6XFxOZ0R7P4ZRE$y?gq&a`dF?nePYG5ksusK6)-Jor=Vd4p&yrSp zlHLKHK+m4AyB`wFaP#65;P-A?Q2PgRG@b0$_%;mS^=JU6&M^HHC|D}V%s5N3pAX&K z&A;^jv)$sE1a>>}S3e=2&z8r(99wy2_FW$d z$IqaRM>{^_+?vb{P->^~GBYOme7*otwP%wPl6P0GT8lq3ygyQr`WFCuvV5CS2L}A? z2i)|bMniB$&rlW2dIikPZgg<#5Au55<(Y3+xWJ4e`Eolo7S*r3pAjVaP{66Q zK^;@y4#_9^&WQ3hW3J)?$SsWZQ~L)&rm0Tg^V$2le?&%Yx`0lDL2BY8^ip!CE5f2F!*S_UQ(#)Ma|YF(e&%cXwB&hRBxvu7Aj9lUB>tO)r-waW3}JxRE=* zlw%bvR5Z^yleDRgb%W@z zEt=wzt};A+iRr-BT3d4&n5S3(Wl#5#s3B1wbKYE+6B2aTPtVVEbo=AXWIxuJUdjoK zlk7}q4kIj8(y%pb6#zz&FRy}r1_ZYKlbCQC^vxuf1V|4@&boDoHmnaddcQ`5_iqU;_3Av@6x;BMfxTt2gC#Fe z`MMXPP`7JwmZ;}ZnB8(Ehg*A^O~fkEkC5U}^WEWe074ov**ItEd1mt#-!KQSK)Nhw z)x13fj{W}qwL(^`RkL_=KzrccErJ6KCQS9Ww>T5q1BeZ8JC{wX7-jCp6_OjT2JrR& zj%Nq$_j@(p4bTSDRsJ6e_Uj}x2Y$ zq>eZe%xq_=kVtul@DEy6bO#^PB(i|NG~psKrLV)vO0lO0r*& zH#b*Lk(6E`Iejl((Etpk)BP|;j6}8_>~Fv4mw)un{HuTS-}+teK#++vd85vJPFvGB z1%97c6fu(+Wnhc-BBuM^NM`dLX3a7nDKL}W=x0C4Lncguy=4d!OPI45eCw~?mZ$WZ zUO~upYSt-@h2g51{Y0ER#EisSehu?XG(>phXMf;-^dJ7;|Iz>Fzx;zor=EZ#o2N^V z@AGt%PcIq+Ac%}ky3W|@Zx^Izl00y({p*PfqU`x8)P6tz`A>iT^Pf5V>EHYNzx~I* z_8kaawwe6Okg)yQml6z9X17hVbfG zEw8jvi!;!!NbXIK)JX;y+nsdW_M&khbu%w&ea}ruNl^!&&eGL50Le%BV05TkiKL+d zA(dRCBDO+oPpo&?A2*3e^M0^UWknepd8|Jfh@{_p=s|Kb1RKl@MrF)VqvEgEk_???A6`u>$T5CdBCs>!RtMYA^UIGramKdq?< zQsdx&2@9G^YS^-7ibd_9;ZqN+UdCE)jrt>To>0y{Npl5r5!<&HcP+FMPG zL$bZe%2E6^L-5cO9ll){H9J!yh32U#-BKgXGvjk{OmZxuk|rGt%AW$``Gi7F+KP{X z=9xuSV;>=zv?66^{Jd<2CPH8(GK0emu8j=nk@(5jubCdQ&OFP~Vber4%jX@j%Y_Zw zJggNjy)OrQS5^ZHC=m)dNV<>dGcQ}ZCzZAtI%xPzFZ${Tp%Jl0nJ_m0!Z}E69VHzq z8$;@yrS<}jeu^*l_=5dRsB4}t0GtyrPAmK~*Ua64AvS>kc&|W1ug%WWs8t-E+N+*K zn`(^)&;eUrtT!rCvd#e z)uD&&?Oa{yktFK}Tt1@Hi%SaI0?C3+OkzqoE|;lQBZvd53Zo)5X`eBuL;^v2K|t=4tI@|X;9E?Uh9`OFZKtj`0i8He4kfy`MLAUb5le!VzMC41#0 zQ6I=CgCTVGGW451(#koEk5f6tuA@x>P!f1vB&%N>Ak?qRH#Fxa;LO*HfZ(;?K@7Ms z9Cs+d%f{rxJWpCXm3x6ChkctfGgY-sPB2D$aquhLtAuddDH}(v{HPbh0A>tv3M-xM z(hvlnt_$p@Dle~I0o26t`zA@c9`E3E7#qz1W+oOKDby0=;@cw*fy~e|B570IjT47# zpaVA8#I#J@OoIq$f=Me^YE940JLH;XF`dF2yb4P4T@N5tiyYlXB*)bSYCP31?7dR8An%0U}w3o?kX%HE3%^p&7 z;=gezA)uyqbRxal8~m#Tw?9&<=UncZKF zgwexWy!{FRYrd1K5yY1xAf<-i^ie?)XDmaY*>Z#n%m~Vd=nTaxD81h|A7>b7jbf5W z>YK7XuQ($sB(v(!8#ZUd)ZQ7dY6^@U$N7=Z!NrAl@=8S?t)J_YN?P^rORhYG4RSH<%HS?DO;@evZPr zS;ju0=OO|>d>?%4#fP-|WptH^pBW!V3Hw|&qnuYT-Ny!lkM+mMkndZAzaxEAvJEI` z?@yhAkW6(ds+Z>N{*ADk`k^p0<|B<1Jl=|G`nA-RrU2I+sos4qnd4Amysz+@kjj|3 zU8@X2raO1vt?A&${h2Aw=~FtoVrrI8hV#wwq!DS&`#~aL>iTJ1IHtR-C;MJ>#X)-f zr&mTrX+Y&cxlCDMYU?2B9;-~>cly=&e0JV}6lo=t4KRn;v^@K0M`6M+hDU#}I1|JH z;n7F?=<+4sUX#$!Ma-sUdBr94QYOHe#uw{HT{tyR_^q9gndvo)GK8L?Aem>_l?ofA zkk@8>gi(L^R_V#AA%-&XE&M@&h&to81|VVpNL3H{4|ye z$v`gde6lr1#|b2#8CrQClM zcl9Q>Jg+%=UC1U%%g(ogOo$0T75w5zGUEc#F2o#YvpITd;UY)tMHnWzluNhFpHF0FCX)2;rNLQXlJYX1uKg%x$@EO(0`!s^J%xq*RR1$m zcSIl9_%=3idtFv*$i}*7`fZtYat0DU2Tl;wd|J_b=UEVBcjoDK%6RbVd=l@AbsQMV ztLwJ&lotU5189N7$eP)xus{EepFe;8&;O-A`29bqCjya4W=3la`bP~o4Jb-dfq_A= z-q_>ccqxtX3BC!9oExju&m=JCe(7-s&g-W7+%H7E5lI4UZpnwfC>Hdb7T*dsYFx4} zC$Ie|r0<@Ye%uK=alp|T+Q>JW0KAH)!3EzXDEf9iG;>rg99~eEp;~Yv**Zn2DWBW! zC_c}xExj7K)YK#BK5rZ1Ef-HAJt$u1KPyy>BsN`@>qZ;7`#hhjSzZ(wJRHnIS$y!A z&C~I}fv#DIx``^aJ9Y)ni|X;=yv{Mb5D&pLi=-z50cw?iGcUq47Vugs#}Hx$UMXiO zE8uD-IwqlNCJa*jHC%u@@!qD%DSvj#i|up3*JGALr}A60v~lH`CU3;k;SBo!|4PmUxZwo3P+B$MG}G}rYMl|S zq!)QUw^|5eop|(F?quP&^;^I7+yD4KndA?C{||rf_kIsG%}m`- ztbmNPE)Gdjjj~%6Hj$t1Q7*IqF>LaxTzj2g0~Lhbbq4aspZ%@B_aFRg|NOu3r=RCf zKbx4`RXe?#X*lCaG*xtWA^K;^!RGmqB#}D-1QCM4G69o>d=mjA#>r=C_qPoHYlb?U(Kly+D-+%A#{(pWxq_HW@hXZgCpV%YN~TU;O58{zB(({jLA; zPyhIjot}KEiQn3;R(^f9z}N6wab}VLjv=pqwYDb7yvzNUddH*cc5b|909b-gJ?{Q> zI&Tzfr~di6h5!kdkI3u!cIKMNq$%S&X32L~^^)jee3Rbmjlp%!YTUB;J6{wiD_|w% zTlv1yy5xqo4PV`>Cv6|oqoa~LhnI6J#`ny@%!kji@<4RP**?{2A;T-PJ%8tK|LuSO-~0FeyZ`>b z|I7c_Kl&H`;$Qs5pZk0M+CTHp{^kGuU;dl_#sB;7{++-3_4R1^SrA$xzV6e|3O1LP zlZ}VMGuaPLD@Jujq9Y7wGBaxzw69IdXGIUOnUVT=@g&Jh?N;ZNt-?S@5^Wq&vZ!TY zy`(MkR*$OQHU2lRoeqc$?&DW-rVQxJnMpdiA>#uBpCrt9 z=`ZSSbJysqQ-Vsjw6Omw6+$XK!%G1J_)Yh=4;^)G+hLsVzKw86oO-CdE24Ka2c;A) z>%OBgAytoN4M=hi@Dw2HNmj>rh5C*sKrhejGT!(+&n+hFG7av_^S$|)Sf>9P?5VB| ziDUS5r3oF$Hb`ndp=B(&__-ELr71-yu@Q9|t6I$x@=SrE6?$ns=xma}q_EwJ3eBx7 zGxzWSxQk+ex`XzQ!ZYO$9lx`IHzl;8P&?GphqVBNZ&UYz`qC28ti-%oo1XSix}svwD7q95{AflizLf1_QXpX^8Qd>TvI z%2P{K+^pU6PNoxf$-J4lizlW8J_R8h-{5<9v>z9@=aoDmq16|7Je^}Y zL)FO*pYg#^dh-)p^IGPF8lLA#))`dZx~>V3s`503Vxw3gd9k!3bsGIvn~}3oZ!`GCP{f9U@OwX zfP?2C-#FChKM6J?eLewpL3W@Qm2A4qDQF2aHZB+cJJu1Pa#gg~dST4M9qNbtndXEM z@;vTsx>d(o;BFxmgM~?Z7O%s2dxc=K%3&ubUC&Wgi`>-Q2A4&#^I_6z}{bFr>@ouuh!rI^4=KKUf@s_e_DNar4Yw> zw(hTNX1>NDkIr2*Q;j{et6}5AI8*ZUGigb8j$F##M?Kyxnv{0sMUF5>+riD14fUHE z4Iwzf5Bn*`vWi!)HU(9FeLo7?_mW1D1WP5`p`s22P|y6`i3&$E-)i}AsW0CNi|_3Y z!1Nps%-rcYJwR_drL$NCj~M+y2~uNS`#X2%Yr4S&sF2lF$mvR#233_1Y$U`X%h-i4 z;>=GRMOyh+*HZ{k#@!!}Lj};!zWW=0rYSJrP!#Yq8mI8yGho`W8W+u1;#(lJu=^wx zFk+d}sMW1PP@D6P8yme2QzJ`|dsuRYR+_i__zD-XU@Y{-?5TwaTFb!iWC^XGi6pFnVQ`vlg!rwLvX!OIEY z&6VG8>BNm8=fH12;F>Dc=vu>1F|}<%O45YOdhhGC^PT@HAaf;dW-L8!ILU7PQLXP+ zdAL%k71^(o?-_r6dqN8$XcxX`OepVQQ;D{rz3py~9`XlP}v^l+X*5Dt!i& z@B@JPW)R>l#E`+13AKA*`jK`8Kj0P8uO`6(j(wYY?=5D%fJ`l;H!RlS zLf+$z5zk|KQO&OUe0_g!#W%2^inqTM=>J9H!DxdXU3wyDev0!iGP=d>tv`PH@aCz5 z-|VdeI@}3zam=={*+w6xd4-NyhYjbiG|PqtF?hN|er|Z7HH0y#7bDa|b{pqATu#<| z9S#sBDZZ4=G#s^7*DmqUmmAj+l>RsE`wrYOzvnvh+MGh^QEA-$F?w_VWIL<#+s>S& zR8S$=UQGx|_mn#qW3OR~Cuc+VSg5+nUg)$s9XZVcn~3J8DD!JkeTtPs{D{+dUKx9y zalZS6o4I)p!r2XtOn{T&-C28ep6B^~Zz+mM^Jx=uN1quTp!+sGSZ<`5h~ifBy~KTY z5s|e1X&_ekV8{iQnfdNx2tuTk0Bz#NBz@MDmDs>J%X{8xdcoG#W*xyd2#39ode=8t z7RhK>gb-my$@U90JLhH~!`H*%K;pP!D&ek`JMTKNeG@UYt3n((Lmhw+KnDps^L>5< zt3(w=%w|1JDRdRQel$oKmaSL5Hfh)(sQ?7#R(6gSup2ahCqeT9H9N1xzb~$Wj!W}O zl8`c{qG!DQkeu3io)N>FU%vN1Mx-oA7TU~lkwEdQl*t=2#k-i{CooP>%A_PHUswN7 zN?()GxIKJDIZDCV6cVylL{dw5q1%Rnz%PIC8_!?(t-t>d|KJbw86oC=BCv;TotunbmUZge2bc|!t z{Y$=|j?k&T1wKo^lX8YkY{apja$;`0&@=7cg7(^{=De&^d8jh>=E79m%}M5KO|UWa z-e3s86>0SC{Rv*@a2e?RVP@Q2r^>Z_Y-wItPP+qQlnKF;SqR>WXdy+l&nK@xc3!iY zuD(tlidgn$sZiY;J-m5IGpY;vdhaXVJAD03fXj)dY))n-kZs_$j zH)`LRPj6CzcK3xbVfujSTelyCNWH`*!j z*S(78GbSP&YXz1ZE~xIV;cCv%yx=ndvrPA*feWD(N;}5_LW7+%VE5X?NU`=Oz8fl~ zkG5j2C@+V9Vh-YijCR7GHOp;J_I%@IrKB=Ap(Dw+br`M6_?X3x!+eHW! zgL-BBlOOxbzwr0{(|`SM{MfHvKv4ZUh&AIubFZ6?A=rdETdxe6e%G^;gpIV`!{;kl z#pq&HfnNHzy@POdaR~uI#hIvljlyceSX?(*Jq52(!m;D2YMc(sTp%sv4FTl{kE8XB&@86M5D3s^{m>D-~F{q+UNRj{kWv8h;xB3b=jqmWzsQ}OlCO|)> zVi9Dh{VVw^2YFu6aoQ`lO0URTeuhBsff00iU2WwAxP*Ukwbt|+w;4(L;7qztjgXfY z^Y|EhqfGMon)$TiWZ_v_rTS@!)(FJU->J){bB#t|L{NjFZ@gY;;-Ou{>}gL z$FKf$W}LZXQ0hr=8PVEaoG01HVgqH1??~{yNIN>{W*t9?W;ZgXYnj&sDSNwC3#5;K zpfpS2Mn|YmMN*%Vb37)CY z6%uUR?kq62#E?|LN)J%WV#^y8@e4+i6V#!(*9uX_E4rB6`JO{VK!lBTG-*6JJ=LtB zc`bT7jJ=`L6uQt5Ql_+{xQjk-1X74rNBjU#oL=~6#&oUF2s=t6yyX~J2H==olA(rd z_R3jELgb0)8Rd!%8NL#Phf((c03ZNKL_t)h$1_D=VxPCsB$-OP^i6N@%2?~z6|?zzH}KGa7y3|IRneUZedCyzGQUzM5%h-i z*LHym4yn9%e|FC99S5&P`k_`{&yLea8MVu5+q?P+g{M&OB=V%uWmU4zOr}@H!g|W}QC?gGK==X^#7LUn7?$U^G`upD1RK#XXnWq4a zL;aAdd~Z(=fHmQW1XHV0B1l+5)6AGOwv9lp!~$rfQP!=6`o0F%Hc3?+2T9ZE^U&== zLj4+_Qb@zQQK&rxD0r`3VFgXxvv%N zDak`AV9N80dsTDh6>h4IYzQ;Mgh8jh6(kA$qrU`Fnak%p*#8UR6P1R1!azD&NSw(h zYanJO#%-lg6w(M6a3y)3a9>Z~%6=%Cg2JHO=JgV5;`3CK+ouDg%`b4o4SQ!kaz8{# zcB8NBYy*6^wyH6yZeXDiMhfk|wy1M9!K4=iRL7cv`{Tu}YvVTRPzA)}eS-DPiaQ1# zzk-fQ?1azF9I9x#-E^4v{oFn{Xg*0Qe~;LCS=C-n)LL|3qLscia1v{jQ7CW_lB`ob zGBf-XAxT(=u7lbQ-PbQt53N<9r{$o=B|>b`;d@5Eh zO3}p2#`Q4@}`ZwM`{hp zM~5o+uz_sH1>{SHtV!8E)n=??K}^K(8FeNZT+72+7aGSjT)KJ9cNCdVauKQB{GuR1 z-VG3k26Bn@q!W`JqU^}@-t+qB_&D<+*kv1lDvu$Pz{EHhW%v@S5r(2i!85e#+R*ru zNaUgX%wSEePQy~0k_Vgv)_p-@ZiTOSTh?4ge_>uCCw2p5JA zAOpRzEq`Wwg0;jQ(ONU~Sq1qdkxhEO1|3xj0K)PCx-Nxd?{mJNWICwO3aO$xPuB2Z zdZerfl6=NKh2pF$)Va_E8`+Z42+lZ3pG%o{-*TY%(XbcbxAb<6Hpk9)+H&xqSHjDtN5&Fm)VZ z9MTm7Y_I3|kjmew-+Wh}$$#D~#5!1E+L;I=h=jOn!S4YgT9*NN5Oy>EUVn_-f( ztrll}o(xQCG{`>D5SOa0K<>n8ovi~1A>aA23Qgy@SlSlGuDMF(i$2lzSJFQlDiCH~ zb7E91?37a*cgkRe#`6qme6AnO)7lGwmKK(OTs(QQ`Lv6zQe%ezvq~i_xvqeiZ^>QhSxTPdi>*f};iFne&h$i*Y8wM04WOsi$c0 z>HtzX3wSn$EZOnVNgTIfQl|lPeA@H1Dgya3rNjTgFC3v~ij7WN~T>^yMBSPN#)I zCSn>VNywLv5I4d3N{qWOJ2HSX8<}}J_}QF*PTwWD3|af}5Y0yjt7FHSbAQ4sp|4@2E(w5LClgm1hwsX!K%aE2{RNs=_=sgAn~B}bFW5hT0s;a}_0 zy{fc98HJ9zsoR_O0;uz+^7k^j%T7fGg!?9;6Mr`SSQ18M9FV4eO>U(N(x3wLeEDmR zdG4}tG_CY!Mg!SHjBnOYl}=RoRuF7M|oI6@P!qC&YXtI_JLAgyX<=qA{hfh}^+~NwM>ZXO#LaS+XCYyA?-BUSH zH_2kRnHmeU$Aw|;o@NkcCVTQ+8Bo1vdU8EQGBp>}?!e6M&eJd(E#D5J0m>{-DmAjT z=`u45(D@oPg*q%@=9&F$muhF_7v4=Rmt?(rwR?OmK`872yr3+Nb0c-sq$j@z+c0_k zi!|1x@`Ti7$aX7@RP2UQ5-Q-fbEy~Vr?)<@JRSZPnjv%KRV!(AajJ*Dh@CD1JKtQYD2R2Y(6IxTk^n^( zJ3gY9TGFe(ce~`DZvjf*1ewCr*C)F~qj5|!&oHOg%#fWKzd-})Zq>Emk?k|;(~)gq zXC)DfMxh9~o&Bp;D4&S~>EfAABqj%{BdREmv-?5lolht}Bg>4KJ_^rUXovk&$NlMh zq(Y`)HM^hB2fF7uXrEOG?Cxi`(VMtS*1&#tH@gW;@5cnSE@SCpfgXaH^nhqCM272vg-`> z!2qE^Ucb(t511dHAyN}3v})kKKRXHMr~7HIoQ;mD7deyqmzzxSCQ45(CdZP5_WJST zj*I1C5}e)jO|RhoOKKz}T6QJLub)@Y zWF_iLd;nzog<7VE(RrSk*`CD{!4^q@BJR0QnRXcNzDGMyO?}@=T9KUDoc!gGACUl~!%y zc9TwZ#-YW@r(cCzx;ptSO2yD+duS||98Lp$A9>P?X&Zoq1!>@G%hfu z48-S4X2!=SkJ#0)&rGp?=4VNOd7e$4=aHG2c@0XE5)wq|2dcY$bbl>W-Y$GXzD@4M z0cfq9-0huXdck#Z9%xc>A~b7!7nv@nM>)7hcI)Iik)b-s0xK)d-q?0%yPbvj*q^XHzeB zH(Ob8lsB@ZA-FTNE8K!E^t(PXnJe7nu6SyOskq1UjtD+wZ=&j zH3Z817=4*j4H_pQr*G1~-fe}8S}#1`lD#0o+_L zHq5wLFd-Ljj%T5V?|5jGEEyFKcb!ldh(dB4B;TYexgHTlW^dK9W8 z&NZX4;JQ<+t5>qkZ#TZ|Cq8!-=wP}^qT#A9I?*sEr*p$QNY^0Q4;nV{0 zMf(n^2T6Z9+d>NLJ(WprTQh_l>b8}&Q-th(2C`e5CN;r#9SkpT0v(OkA7D(b?aVy2 zP}?niDZM*fsT*YKr5q7y0TDC4QjBC_ie7n4;D(PZLPuiyI-^`oScF;azAPECSR0 zq8@J)NO3x`&)qAI-{^~GH!gec>Dws|v*w9FeR z-3ap7d~=BG?#UxFUuJH(u}R+A6l&INQfVd`Mr<^%!rhy=O5tYR-H*4BFyw+BZ*1*0K6X2Hy5u$GUC|{Nlu6yfviI<4JiBS)zc)Eu;;X$Gkrzrn|@3b zIfir^SwI{mtF5j@H#Fg;1W!m{((k_*Qt#`td`4hqxP6sYpBbeLQ43@psp+XITfNaE zN$vhgt_YFyjZ_--dwAzs6%_)(P;OhoDk6LdE{ z#q5l89p53!Sm&AY8YO7?a2nP6uW1vUXYBl#U{|p6%;SbAsU^Jut+dsmG6dp>;>RRw zU(C#a?g%-zMgikq`Axkk?*{W->!3czXA<+npe>J zU1B83yO#Fl^UTbK-E|7+J3vZ5lu?~*MxN*XYKVJ=anw^Dw&`F@wVKw9LwQ9A<9K*fP~UL!E+Q28?1#%HDd#un z?JMrLXIxN8uFhdEpH^18eI?91!obwu0SnIuSTg6B+e8fLK)3f#!D7$}A&_MIb51QZ zp1v2BnQmr-A843wbg6%#v!r1J&QHU~tGB7-Dtx*Vo7C5$&u$6HrZQ|Y!Y;Te-E7uSlYJOM zN;8s|w{Ym46#CL0Fc*jGzml!#TA#qiMtFhX?xLG4!G=Qut<3A(&rAZ}cr%T-9-bom z`7sX{VFqBH!zo*IJ8I{l_6c$HjeE(|&&u8t$Y z86d}g&W~y&K%L2LQUEx!`=OgqE-&%wtL?ch3lK6h$z6+Khc6cWG3TOc#EVRQnPhZp zw#IG+&OA4kAqJZa#xkYlNE^V%xgz`Id}FdStMfdhsiMz@QBONl32*gs651H_C2CJ) zO)ANWTij-1mVDQyYBn=&<7_(GtB4}ov!^K^WJ|{|DJ?LJ=|vCLcdcGvZs$?JV-h9n zjPPEAK;dHZt2z2`7LX8GhuC}uML{>8883!I+XL`%D#=c3QBI$ELPp})px0!7*2ySg zG|bT2(Zvi82ArAQk2XsjP*C_+Uvp10)~K((klW)+wLIs2^*t&Se{#bQ_^N$wQab^H zAr``1GuQzI2106 z0IPu^jz2y-HFuzn5Bi=f7pC*m{ru2tPzT;*cmLd<`_13}?SCT6AN}zk z{_gMoz9XrNhXP*Qg^gW>M9l7X@+4I%S2bFQ15UFe`W*>o8+lOld5Tbp@#7=E@ymbi zum6kt{QOUU_1R@+$T|nkqn2XcdM)V8bWJ!o!)NFDN?=A!Yb@z5XO_>I!d{+AB(u0n z>z!!ulU+>=$R8_h&070z(xOS!E>#F+ zfxr0WFaO*xfAh=vAAaX|{^XDUq-Gdh>3@({i+A+n4M&DV-YJwf+6*fsY#8c(18BdG zt!%w(4gwN?+RIBoi_RhmqGQ+a0fB09GYZVi58_mPbbw}@G)C21S765cruQccA_sY&<vu5d#b9x%;Jo7V1Ic9Ca-gw>i09u~T=_S7KkjFV;N6-`*;kn#g! zh715S(?5Rw?B_rG+yBGw{Ja0=zx79d^e2Dyul`f`{C)r6Kk(Q8#^3mp-T(am^`C!! ze187*08ZCi5_Iw|1qOCX4a174}Gn0?fr@+|Orb_PAF@ z#TcSeQXd()*0^3D~7XRXNv6!aPz-b*?jpGPtq?Y}Tn_CV8CA&IJ;R zRg&rVosWkX6d*Zk_U{ag*}JoCW*9q=4#_x|-qk?I-*>(W+4j%j5VOhSV8EXB4EJBB z#?q>1{g3$&;yB4?W;nu;B+KKo`)T>?%s{>DrN#3h6;MV~o{iJzHaA8m3~ctn;5ZDJ zww`5lLvII>Sb~J7zHg&$TBdw^^eOdb?p_>8P27WIOp=l2U@OfPB-_RU{r=^wuxv&E zuK*~ab9f2#SJX_mAe~2ssh`h`Nzb7SXU6moV(o5$d?lukGI>AZ^+W=;nj4P9Z+C?z z;Q%%P zCRE&%D~ZzCUo;p7(iqdQKwJw>w5%B>N(F0lt(*lRFd9A%pG&Z71D)Min7ZSr(Gf_t z>*a1i5TN7^Y^c|yk%6Um0Z52CJE1%Ip!ae>a)*#BR}eWWlR*$ zOf$V$#~Z&$RJdeMQ%kp9gF|+_-G*_>jg!QjzdOO1EHNDod9?SZ4T>_dTP_vCgpLVw zQnNUt%co_M2hXP>w{9#?D!eQMtU?Bo>XmP3YZxUG%7&os-e&QQrlEg@mw`kib*RnQ zyptsjTtWi)#YmbH@gmvx;9nhu)c-3Isq(Wd72BFKA@fW=p$KNUbnMuZHyx*&xvj5( zaLiJyfj~Aixby%uV$w#TZvh}lw+t@42r@8|G-i)XISp;pbw|6`LKP@d+@oaIK5Ty% zPZX8zyl5_8FZwj}S)CkRc6?4!GxNBgp~c9Ir-72&Z)uz#Nac;EYg!V}^h7o@l-oBS z>TRYM7eQj;G_OGzEDq!24hddxK7raTjPuFoMGTumcKh+fXWacEUn=>S>}DjQ*#czn zu$}IHb&B}l2b?c@BZtDLd8^+=j1A+Rag`5Q7!vAImJv$=cP&|xO+3`>z!hJ3eo0G6Ctm^lwBZVa>^Gh}c{1(!e+KR@Pwq3U0Mee1UDEbMvuTy^TaWy(lkA#ic| z3a+vZLNX8(IfO*v-^`at`Ggb+DM}nbLgJ3ZO;{E}NGTg9IF-1{u5;>Cy}#b_q4l}m z!`gf8{kzOLM(?e+-bNo|-sIDXhbG(4lR#h`AxYA!Ty`bK$?zoETL%h~PZPBvhBN&_ z@_sJk*h|Y4c@FRgU{_{|&h+yPh}%KT87~(GG^<^Obl{mfjn4Q4WXuq24M|bwjKW~o zwe+o9I*Guv`s&l(uiPb0=UJFp#q{iUNt(HB{MrXXCA;(SFRcxu^f~1~O@%OE2(bZn z`dSiea+fKA2UE4RwJh_RRU?5pw<{_+AK{Deu47%CL z1B0KCjSF9M#eqQ8rmLqnAcoE(h1@Ctm%`U?12e-2Tlrh=v9UuBFl>2s6r;z|l<+*i zVJ{7$N@WC!@Q-r~B$Ru_qlwuu(ZhI*>Fhg;GLz3ojAZl7Y+8YyD$g|TBykwU3~sib zz5#(O1v+z43pV21^O?G<6g;*(Vm^S7nPd5yW>DltZuZBk}v60jpK%)_T)#Xt+S#}O&{l0hC*g`|6wzhxxVUpER(PpL{1g|5ccHls0p8mbw*vK+qbOUa1G3GI zM1G1myjzF!Mw7b)p_ZHz;GK)mQut)=MUY6V5n)8KYRAbxT?nU?s z#>lj^>NZ$8j@}YR9q5nmzcs(_-e%Q#5o=SmeFCB8UE?|TA$ao2Ywy_YB z&T6W;w4neIv-{`%+;9D*zw|E*^NT1bu!Fgi)Y-x+kj^%pU;bJC`v3NS_p?9u+kf)o?Khm_J~R7rO0ngjsO-WUh#1%~ z(2ic}#p|*2>414pDgA8x_AKG3F3s*upnpYSopf>*pqC~{nY^J!#y9M(CxopX$#!A* z3i(~*)ie?f{QNik(|`E)|K0!F|K)4{WN28ODPTZ@4!cPCjKohPBCGz$K8`_T*xko* zl=v!ANQh%Vg*s05&`t95-}>Q)AAS)3r@!-ee)Xq+T3tm^k{8ziaz34vz~wnwhc z@5YCO^rV)08EJME5|fCgb?`_dJ5ts$k}!8Njfek@GhF#xq{_(4Hl(~tfeKtUA*2(O z0jg9}lRTZccef+>aRD1UyBBKRe}HBIbrF(MqIOk=99ViFMLZSe&U9nrD-0EHHR)V| zB$2RZ8N{@}D5mnDqho&l{6_Z2|KdOU&;Qf^hTYvZO{@v&6;T5`Ctr{`uP>VDv9bt#rI-GXz$O+`9^Wbm8prgiBcfon_#o<&} zvg!@&rtCh*g_~VKOyZncsr1Pq4rJa=+kE&b4c;HAF7=JBJT_%sFWyObkxU3C8K>i5 zM5^=T`f6}nc{o`6ztdG^5qsD&+2jx`=+wJ5`@TMV{}-gGGw0Gkpd&kIc?hwNAA6EO z+6(5Ph>=UwrV-B6mS5jKq?!1dhqA&?G#-*nbI6cKm(;PFRGd53(%k>Vkjx#2#qy~p zwMT)B!?n|J@__SFd}oix>jce_$nDf{{%Eg*|w zH*0Az7t_}zX}?p@@MXo=B}!k1w9AN6R0KEc%p{alM640L1p>W?(3v;|{De7>+Aqm# zv%tzjTQas8gfi$m9nP^fm#Y)H>GY6B&G*}2)Nw**zHj(>MLFnC>!Km?9sB@NJYK$XY>$o1Tmxx_h zVmqq>IIo%~wYG$^^yRhFH6~mfMd18OW#+oGZucD$nOtjJFmfw4$@=Py#k>p0NLvvo z1&X90| zntpraGAP;Wo z_A9jgLQy1W3pJIW)@RynF-5a^p4lwC*A$J!H1pfHW1I2%xNiF~BH7T~SD7lB4j2jB zK5J^A*d-rGx98Y}SF#`}wd$+-UFzMDIe4?I$OUP!#A{b=JuKdSc|V6=hwgs7Uqor0 z-_N8YfRNf>^31hj4<5Lko|3XOo!)8L)Ci?uZoYj_e8&m_b$5%j^{qU=c}nf`Fw5a{ z79xpdDu$%%=HFi3-t0osJ+4=UPBE|4nLDlI;H-&NKb`L*N^kxjuA?0HngnN(Bl#lNmBPqF9B}ZMp`oT`sUG~Bqc;~O5;KT<3smqLmr`u zDT`M2ul^JD^bKZIDeTgn{H*;M41so9T{&;<%b%RA=1F!7nf zCf0P#Bvsjf`}u&T%*f-j$&%#9*U+UXzQz#g(wgc!XXZQ);q8yP=|HjTM7jCOxp!#( z8g7yglrbH6Ko~!_F4rRCl_Ih*Y8NrPvDaFA>I*Sl|4-h6w%;)hu=$Mbj{!%W!0zX# zx+TF5=0jah`m2Uq*CW7pp=TWj-Q+&_Zer^V)Ntj1={REUv|7-RPLVcU)o=6dt}&sY z(!l5X56hip*k*ubnASF6a2Wl3K66`S3T>T8Uu=Nhfm~kc51?Hf+K_! zFrjt(Pi>MTZ9;B$k}kHO5?p90%<={fM!E!_o8yZ!4^miwaMQIHu9>7U9Bc*F=A_;q zZr%O0v;W@6d+?~vIH5T~xruGUs9&z6R-JS^y1aY4H-7zByx z@LlEJpxi8(Z0>eiXYHU~I|^wZ#&})nUNkd4I6#sz?brC`IHt^z4tAA{c`rvh%Y&nn z*eu_yUS|h;IZzFF$E_qR&EI6d?vBW9+50B}#tuK!avFm`;!I!Kvlwz6VmCTN_Vc6R zo;9q-DYrpVz^*$5Hj55qe7{2gda_`5sf|M8F5SBuBA4S-0#Tx~WV__(Xk_|*Fxdp@ zpwL*7?95$iLTEqjBy5&_?BH4Ewpy$5wbve}2qET9Bn8Pb)8NF_u}iiPdy*EfPjdn_ zGlbe3EH@gSjN1&XX?Vu6os@x9$IjZ$ylGoeN)v?b*&%mKBw4veZ=QQ^KY%PfPLb_6 zB{tCQ$d{8zpeKbCf0|J>fctr4#aSPQFjJrUHEJ-gp3vNE`apK46ZOmFPEHoD&4Qde zPInF7|CQ2dc#ym5yUmJ>yK%5le6=QT<7J()8?PP-L?=3Pb7cHBM_ewCJRp+$B%GaI zJ7=d|_l7#HN?N&nBh~ukK+Aqs8<1nk%3!m;9YMDU)uy9d0fYqlt~VsuZx4#xAeKd= z--(%@_XL7J{^LKMj7rf1Am+_9AQ+F1uOq=wfgekRi}V||AT5Y>$UgJcJ-_q6?U~eJ zwUVQroH!;v(|GyaT@7ex!+6ArG3QK~4R?~~i(@w2ka5AWud{cHPCZY~kx*8Q(@wuP zm~nGQo!+tNyQ_dywo&-(K3~1a)%187-5Qxjv!AqWI>$?N!h%_5CdC54pp$#*7$k(u zfGNZU5FijmxNS*hp3mn~QQR<1tvmXKIITO+zW^pd5$|e#Zp9&L-4)3PYW_M85t|qk zr^~lcn0=3bCn3tTpMuzCOJ3+0K@vG)P#)F2fRl6Legt4LY@^Z7rrTfiylkEs0|{dq zeeB5GmYlP*u1njj!QJDU>KFQJ-69@C`_r+<(>t`M&9+VL zzkQE3zcy90K4HC$sf!E_ZK{B{qY0zCd!HluV6dg7v@5u>z9CAe-|BWZZ+wb|;gL1D z&F%2k@hP<*c^$0xfz>Y}9oDOAF4Yho*n}{?0|M8KAg?@#@R+fQ(rf~H-o~qG;EbmC za!?u^AT|No>QxvQ%DAt}wXZaM)k922=x*;dYYqG1haZ0LFaI8yKl#-!|G^*qLmIup zoTM;Il5q-Gl6KtA=sx{?rfU!ooV&W9bc4V+-G@0v8|x@&^cTPU)j$8cfBB#KXaBih z{rF=C{%4*fGqVYc&t>a<@*0B@4RjtYUdl`t!g{VZywrLT-nOMMzjE>}a8L6M_0F|Fs5qXUS_-lSMuf6VpeGQ_@VEb`zw@Vm`X>%O zF$ID#p0}kH07+S1``V=aVmfQAuTeW{BBCkvfPlfqr5mG@>*f0E%d(}H%C|G7;zA@` zaFezLS!|lb3VxliVqyr;nU9Vcx;|98#ZaPksSY-*rWBxsg1XtJPdDq+-)`_OCLt## zXK%uc5Jjvw;d|8EL;15WY+vf=ls95+iz4z)AKqJ@ta_P-u6v2e#Co&Y=VuGk1usf<4dX6DDBi zJ#8Q2udiaXy($f6d}Eo;IXh8dxSaZ~{^R-xMFy!ko^kYKP|Vno``Ri~1B6G09ss-Fq6vsOaZ3;JptafxwjEk64XN zcziU7KA?*`R+Hl!H?bvHj(49 zntaG+CUlfky7@}LJjwEvXl)rH65n*f!8_p1^B8(t*Qxp`}Is4~7tm_oVa za~?9#=S8>M+7Bd}$xQR%Va6w{aw>9vLX9KXQ-Hn~Y@_E7bgsW0XJfW?eic5{IHr4* zf#pDtVk$b2Py~MsjvxcG5@NY?=Dm*8DVntzI*`NUk?FDeQ(%*P1_!FFKk3E+gf2O& zrj2ec5(O~aPihNJ%yhh$wehOv2o;1x$@Y{=cr07V<)kxk>!Qt5FCR+_UiKW(>xISG z!dxtN!uo*MSn5|zGB96BsvEVf9yU580h|G5p4J|S$IBF6$Mi;G7bK8gfL?5q9waDC z^P+{AFeb5eNMKo@3v|lG^c;RkLBkF;S!_IJpntC~Y34^Bi%)7jF%X@+M+#?=BUkIR zJLp1_x4mmENpNOH%LB_>X^0gGdFFLFEc9XkiCWY#yMx8&<)^zJ!+5ol)TA9+7uC|T zaUd!i5e4;m4_TbqeR_JKtcX9vuvsU@oU$G|_z6BUy`SO@UBV|N8$vy%ziAy2m9E{@ z%Y=1W^K7!0s`(7^yi6yrMD6C|OzKiiXh-%!y?ZnhU`m+R_KZmM*>IuBS|SZqB#1Z% zm;kgeo+OjX65z~J=SmOhm?&Rg8z(EP&-k#PAs>gHsZ@mu%toBA6Oi_M*#cp{z9h+} zcmH7_JOj@2UWn0+XZ!Gaqb#XUAJsG`At5uyH*&}PA`zraPDp7+<5uoDHi<|{K)MVh z#;~6qZENC8Pj`>_(z8DggpO0mNNkx`NE|%rY!oodj5Jrt5$o0p~a zvvN@OH6;l&L5k`#Ad0dY+hKSD!zR7S9W&8XT8DI~bslVO+$uN3@1zKyu5Ty7D~hYR z(|bB13vUc9S#552JVrst*)K(L!a7uqB_OD;>=gM znk>OvDRu0lNKJN#i}m9f8QB=Nb>u*iMM+Q~H2m|_h<-6Suy|~cNF>r{dZtK`gP4nfxLgyuIzvlnB3wy*D65fkekd4eIVPG**1k_-ZGR#sOo_%*j* zRJ(<)B{oK3(|Hg;e)gL`e}4NP`3t}N#VL=R3=io@}w;4JZlc{+Xw8j+s~MclQ=q1lTlx=B3L45poGC4oPI*_Qwk+ z0S9&OjE8m{!ZQ!jqCb@FB%GJ^3xeKJz`jkZE4`@o#Z_I}?XM5bW}fB-onESe-SwF_ zE*Gi-eRCeIL1)LircZ+vx>NZes^e_MJEWg$KD{R@4Z$Y&G$?RP=ZXRIO^=sg?`95- zn1&^`-x}B{dVaE)IT4Pxc4GhZYrz+{BoJmUm8_NRfkAbXc`uz0v4UFbuVMI3ms^_{ zueV8(bj2sf^6s7^#__1$zx;t%iv=>zY$w|SJgor;Bj&@(hG2R7~%VB<}BlPd@rL@b86Rj5A5#GfBy5| z`%Awo&o6%Yi+}J(|Dfqp;|`i^QK;y^b1StJDcN>3~4+v-=J> z=p1JcnNyr2-kbMKMj8A%oO)EV!U;B^# z@EiZvfBXOR_x{fR`19X*n%K|GvNNT5WRZz8FFr1qSyWWApVB7*rh#aHgoK%<1DEbi zGpf%;0N{r|_rniA|6#`8|NYSkwd z?l}(l0u^NDk@h4Tdj!`8C>1RNwHg@GL{q~&Z=B{y^30I$4FI0!2{?qVFSMZ}{GrW_ zcQdAfAg`U+sEkQ&)TpqXN|GeQG~=fCc0z=VJC(l?=sx!X@?pN7njs;dX<996OZgU~ z&Qv^7{AGl!)L?ZIWZ|*dMFZg%`TBY$`7i&=|LQ;dkN>0J`JI2}pZ(|l>EOTmzxr4H z%3uAL{+s{$zy5oF@CRSdBZ$r{$TL zRP80)NK?Cmla?;hyJ9%KnF{LktX?}JwSYM-Q%ySUH~aai)le~ep4nq45@Nm$JdaG6 zsmV>s`#prfQQ2e!?PBt6DTbSzT(HEqKppg_{2ghhw{f7R*=91BZlE=}d$g-Jk5C=) zB7fm%-+ikIZW>Q5U89Ph>cbf;c9gmJLhMBQjp|SRjxw}b!7ZNUb2q^A>*=|nJkRs_ z@l!!gT473}Uul_Lf1y6=q}FLZnLZX;o9`{>p!y@#u|$r!6lj?a_X2#wQ$T?`%cM0q zfCto=(zWN_*+fT45(Qh6bgh|zd6e=>C_f!rg7)gznK?t+#WIg|C!ElaruLZgi!?^n zhrS+1$k=5iVPSZyTT4Wt-rl$X%^F7AWZH$N@ZR{|2~P32OuYCuHDLZ1g=?F$MghQa zJ?K?gXHoNRroD)pKyukPB$5V>K#kGl>gU>Rc+KSg6o{wVh*8T zMwb$(BbR*?#qr7{bltYFmFK7Sat$_Qxn46#IQRp34ZW~mpU^;NuD|a4lD~_w}9Z_mcs%}&qJ*z3#s2VAVlsW8xv@q3<#J+i+>x& zVa5$7J6wDn!Q}fj(}~+8U6izUjgrz};!C@RZf^0q{vA8Q>A*PJgJj0|M$bX#79YQ9 zLE}p*^Az2SG&aGz;aThH6;PsKp&vT3@X~3pY^vj6sx(nj#G3d(0aE1+jmzp?!KKndRtM}sdO$-rz zQ`M$VA+x#s4UI+qwv&m0-u+7`p&V`Y16iCdK+8-gGT-g+f?t0Q3MRfWp_uj zAfua(i0O6tpYM9(aGq?EhSQni06w2x`QjDeBsFxGrv<KGnyNS8|KmufbaD>XNLX!=-de1_ySCbtpoRy$Q57?$vi{YZFE+6p5ETo z(329_d~k8zVR=cYR z%@1B03#oS-W~N6CAEx=>JA=+fXS!fw@-D9 zNARo9-kK&|($zDZF82-L$4!@4dO6fNR~_Pbq2lNAKJ&Wy+S5x=l9}r28LM-LWU&H2 zg5PyjFt8&-@9iA95wk%)etzLQPBktvV3Q9nFxoojY7zrlBx-6FXJX5o;YO9XbIH5En zp_?Uq*>sT3Lo0AF<)?ki5%!JkNY~t$myU zP|xg)_>FI7nEBeQHYU=|Ti2DAm7CKDs|EokEK3K8W7K>#KPrdD(rudv2AMk+?T1Z4NS*&x^ zg}`*000UcnK0D_i>Xrl0>1l0vxkhZUR(I%LD0TruUhS;+ZE$q+4APoNbmSh^}Xw-OQ8Na%?pw@p2`in-51ZTiL&*`jb>;3xe>$am$41Ljt zyW*sKEI-bewE1&bPi2x6m#(cSVU?C>*Ti+YJRLVPT@N&GdHoT)0qPyL4P59EN+F60h3Ezes=)fdfP@i#NYq)qfEbR_V5KGy(Um_*oX zG&&J(bSR$*r-Cvy9z}N7=^DCaOFB#0k0(Lh!C*zQ-k0|=rWWn7DZ!^k$xys)Am_~G zD4XmvBO&Wur?s$&Nh3cnR8@g+B_!epg7E6&%eg|-QSk6TI0`_%od6|#b(uzUe4b=S zXUry$q>a6!(R>%9rQ&9P^JhQ%z2Ey?d4Bb)U;Sb4KoH8G+f4gjdy_rmR~qQatJ?u`5s!~N z-;sWt^pMUxORBNpXh+jgf<%X1-O+#KhoAkuzx_Y_JOAhZ<8RH*OM{tK53xo3AWY6( zJMCfN@mSwQv8h)H3Bv@jnYH*@ghCkX8K6LlBJ6Md{DzxuO3O?BBM zly6RF5e&XALeRQg8G;A{k!-Sw3^#c~qijOr+3dr_MP`*rP!q?yg->d_LEhU%twjZ~ zUxRcO&eYp}sMmG3V2;lPFf(QseIDt++fAL#r|+z-`07{M;DXE@HMvHIxr9yenQru2 zQC<*rSbk7HAa?AoRL%V4oC-D{d=4b zVR|*fr#MVU!?KtI3UHyUu)__=c|`Id)kmRcRqn?wS$6;Ofpm1M%B{eL86L-OnBz0 zeA&@#ruudx8}jN~g|ND6@l3rS^c|>7GBeed!CN#Mr=h8a$Y5dh_;7CrNOjbmG1w1p zeDLA|nj4JNXP!QPnk|3Eg0CTJaK;{`?tZ1uy7OIDf)$F!?fZD}nMZZcS`?(K9&u#j z6tJ87^(TdcFRdaGV(Z&)GbLjEroPvj0>mXX4ho;X)pw`*T8SwCOoE(Fi<-pT97d(Y z?vE~@kAtibj{h5LOadiv(G#2*zRlZD?@S9=OETjSMsX26NAauA!do)x5Vj+QVI0^H zeJXL{?q{0Zlnm{rxCE)o;E|DfjZ)k~L!e!(pZ1nH8DKh2=>-}r-a;i1nmf$t5&<%` z-x~M+Day^SIriT0x0Jfgv(c7zT><0)iGicatrFJKH=sdCn4 zsMGWwV^6~OZ58^!NHtN1DLBos$bO=&X8=%14CPPrUD^bI_5R_LFcX(V`50e8RFEp+ z2c73(ai>`n)LuY(N6o(3^tcf>a^~5^+pFutWNa$ILFB+4OziPBWl?6d}!8b=rWKjPwNmG zB-M$Nf}X4mUqGuymLse*poiE?bmio6p7ueyaZ#2$gj3jw#vBDtUIK>m$Bzd0sYq+6v#(g)X(^ z+JtlM}V|a z#j(jhvH6Zb@J)Eb!Q1~$wHM00aW+4u-W7%W+lqaXO>tx&8FIsyZB2=V^-QDKU~XaQ zwL_aFl|?qxV9~L#17y8(CNz?DzO;+uF6<2u4I{66mJZu1CW z2>S|4u#Z(6+0nrI8E`0{A9FKHn;7#+=p+QturyFBI`@RB-apmpg`siaJ3Sj5AbEqI zboI=(8CPNtJ%$y{t~j#UBq~X1K*BTtb*r%jD`WeFVaSdb?B3sC0Waa!2s7U4`9}ew z<&{S_c0ZSP$%CUzaQ5@D7fN1L(W#pR5FoCt#?SF77GXLVH7u;}ArtScT$Fb{5u~A$ z(bJ5GQ~DsIlXT{810*Hf#E)Ylv>e{Y$Kybej?x+5Dpi5E# zG~kN`4PZC!2TjS)Em0Ik=U&!l$jG#?OX~RN+P))MGR?{^6Vm3m1f|sJjZkpVYM!Lr z^$>`i_wi9a`{fqCRCzx;T}893@-ax>+u8})3ey-3$*GQ1d>pi;C#c^q>@B* z!ad_llNt7t=dPsss0ozk2AM_$EWh5*-<)9Vl$b2T3?hn=FprRNoasWS+DG05JNEHFkwIYhKC|=uuIqKwc28M41=vMueOD1BYjV zlCrksm ze08QEu>Ui&=H?q+;Qw{z$`NIO3aU=G0=@7Gf^%cc+duonQ!ci8S&5(cnfVw=#cDKT9&4p4#ILQd!C*d zR5C@{fdJEXF1BS4;l?P>%JL-z_vM6zEdBUhEZY+I^2BD(S=%Oi(wP+DSL({3QR2LZ z@asa^m*7Z@oe^LA8mHCAU?UMu*N3Do1I0F<%*^IE-B6dN&H%^dHm{9f#kU@mYRGtI z_wE|CwqB%KJTYyDrp*qy&l1tWM?cl z9G)n83uy1jP$-cjmYB|yrk9>_cXTz>*OcPPRExj=N0I*>5MMBDE{lD3m}Q@@hq zbIx_P;Vs|Z4@YRZ1H~+aynbB=T!)uCNXX1XvMIy*xox&ZCU4G)-PbmRwl683^bfiE z>ZhjgwV8z8Ea!}1M&?b3rvd`En}DN!kFPkwHxJW%Am80T7l$xU>z`uD8{#HD#Y6`0 zYq10^<-fDI**gcfV!H5m7*BbNXtQRymd^#=Fn6>51e2XP97E3$E}Rh{jY%Eqzc=5e z9VN+>^<~7RKr?OiasHd7bIp1It&&iIVK_>*7$fBya-{;&V~|K?Bs?2}V+-=xae zGI4A-mWu6XUrL19mxZ3nuSvqTtSU~4Gwg2P(Cx@AYrLF_Gt-eXOvoP7QdT6-Xz(oqaA<^~FwX2@tj=5K-_Y;bM3o zLWqcnKM?Uk#1nZyev9$~1|^EHNJ4^R`wfC+yUG@zD7MSS<+$ps(RgTmu5-M;Z|}3$ zTw{*Wd+V)V#+YNyb!M31!#h4b&forT{;NOw7r*=dTGhi-r^4@Ogfi_$8doa;JK0?z z1Ra%bLKrxo$8p(QSZWkW+MD!$zxAzez5npuwP1Xggo@e%s8 zn2vup$h3TQt1+FP*q&D|qFao+Q;W=)Rn#_cv4EuYk8nL^p{}m%pDLndk#%rjL0Ul- zQvvp~s~`2&mUIv+uF2=Rt&8i9_M~w`xa%BI6K1sjh1A_Jr%M3jj)YoY0E=a;T%{q4 z;d!2hDpaTJ1;04tOHC!d2q`_>(U~#Ko4N^w0d%PyL(ByZ_`r z{p&yd(?9d;zxL0*|Hps)c)at=7DM671`FXHD2mRxcRysIm5o`oMutTtSe7ygAJ zog{4jsESlgd&W!0zT&=9MiMhKw>&pG+kOS^$R*uQwkvj>BG7<~CfRxkM9F3nR{rET zqwS_A?mG>4;;Fd-N5^SDVm4xQh?xP^l;=Hc&y^j7v;N#6J0Q|U&?+b(oZtYr_tjps z=QO|ezZx2FdSm*_0d9QiO&gkI8s6)GNl$LGQ}ziVcsnU=-pNH})|zm)Grn@HtWQ@u zrGDR#(v0jZcU%&NkmF3wsn@AnE_S>KK>du`3Rp}EnE+Lkf&O7$Vz3p-8fCkLp3SDG zIFI(e^msu6GbP2n8Zf>-V*jBd&a49=&c@hEh}uubjY)OA0do2PYwsuWr6l zyB~p8qE+a&`JE!{Y8|tELJJGk@}T$%9Rn8G~zv@kU)>9MWOM zS_LQh`3^VKTP zTzB6D4Ab&j?h|T{yE7-ZYe7XrYT{d~DmW1AgVr5*=H&9x{x!4+1rnj)1zwVs2Xw;T zDzkxd$DD-g8eU~gP&bg?eAUwH)~KHptdap1&-OcZIh(bR?X!>+hx$wsy0F^S;HY(5 z#JG}rLod1o>h!|uUi{C z@}k|KB0z|9L8?KZaTyuT%+;D4L1zgNkxuWxsYf_o)!M?b$qS@8_XSABxWkyY84+Cg zcSM8wtg&vGcW$N1$zj~GY83+Q&;Ffuw?o%{-f#oq;A>am5>k6#Q6vQ=fOV@vfR$Is z-a=hVTT2Y*@PhwV=x#Bi9u6^$y?R?F>n{pU+06pa8n78*W+vWVg)v)Sz)hcCd94r8 z&~{OYGGm7!2~q4=TQT+LHjqX~v$X?m_H6xdY}pz@S4(@=gk3Lc_m0Fsr0idhhgDhGl4A z37Ak7tWPln@sP*0k3a_)V`vz;!`jT03Q^L!@y@#OrLV1H!#HZuSMM@;Os2+j6Yp@E!I;LC^sQnFO9{j6rN>!>ACPaUzVH&Q-~h?`FV zB3&jb@T7nRdc`X>dqOyRTcB#OKeh-!0f~1;n2Md^nQC!9*I!0Uv8Z01L{vPHRJ{Vd z<1IGoW)@-2IafuITTOHvR?4~|Pyi~P)XV@n7$WqsrYgxSsh_}g4Gm{!KkYyDy(%wv zB&5Prk8FDYb7vo-^g3t&)v4P>OhULiIdY2BGU`R3{bzMZGB%GdIF`b7GquzyY%P_o=qL~Az(4oOnI#|qW%YRt4PYtgKET)QAS zPZ>;lH|!U^c1Bq{zZ|^lFk}U0<*zyXan%stEQ0TQzl2;)~cUmuW zPo&?I*LhQL>=je3g5o;VS|9*4K6%pzc7z%^VEb3zq>UCw6|bylmitgsfj( zeP>}n6q3#8nHI8EZ`M*I5h_(QH$wokp=O8aHTp_YkUdLIymS{k7B;I*rMnNY^jx4rY6R`hGBS|N4I16h7#OgTrNc4_$GjfhV1o=3 zuJIhWF#7W6*-~Z9$&~lC^vN`|(yr5ZloZ~vZVx(*m<2&i0;6HvOX<~n(VSg)guVK@ znApF)_BlipH|W=yZf$kHMvAjeflL#e>S3b&9v4|TWpZahLjo*;^p^joS=&Daa^1nL zIOrs0W+~{ZH&-B{!aWafGiwjJVb;4%*hni5>~w{zMwM;XUocCJvG+{&-09~3@sX3{ zu2u*AB2N}f9hsawyCL0r-91dmHIIguK$2Dl0{yk%uhBz>*|Ojz=$sOyAOU(1n_^RC zRY=t7wR1)`f~O#1&R%C#3r7V>;;i3w47o9HEZiPeDYr0&%GB#Dt5JUw_OOsAO}c4iU!4Xirxg!?fzY0*m`@@EyJY5lg<7aURVTnYX#%g~ z?I~D$*8~FtQSrJjWmnym&oK$6fF*fc*Dw6SU!8gPXMgs?Kl=V3HPksCv%r*Y_x8DBlfB(0B;~)S1Pj55R+i0F_-F#vTwKNuU z*!sc2b@@SG4T3ZEY|eo=HbjoXZB>CNhob{v98W_0_??gMKYUPs`?r4k^YhuB8`;B> z!F$0;WwbeGw9m6;k3@p$PY+Ut05*OCGVL5Q?9*kQ^yY?YC@q0NO=2d~xVcH^3-w=- zQQ3HodbpR{Dt0>?pqJH0#+|k>ZpXr|S0{_!)GAD4gxk#^~ zG2moIn}7QgPZkjMEhP9tr@REf z8=>(weW7afkr2BOxks+&J_7;i#mEC^fy{u@Gd&#=`09_o4qUGzJ1to=dF}}{J0V1I zCPMmaVCrx)!+j!bKPRzYaZ*h)tM3grL>QcKrfGYc{*W^1cp!%+5P+*?i$PxnZShxA zF>+P**Ks@~&Qz%b-N#p+uv4<+hDZ~S!{L|$1+HBiN^7=h8JNnjsVawte3?cRtgM=;Gdn?kT(~Mi78&SZa z{xwZ+ECb;#CU-g)xf*|(`}_Elt>IBuC9a^ZyU4g5Uoz>EC(*(4@~s`Z88B&9cg8iA z_lCcsASe@uRYZ207s}311nBS!OCbapAEZv+GI>3+O8Did&@*Rc;ea*3jO0R9H#Bc3 zB!GH~VeBfXuS-1-v8{y6F}7s(7uceP8nXJbV$ zb7q+1Q_9iZ^Su&lAQCeDhNjdhdl%p&YP2pd7w2T`Ji2DOr)cdpIP@Mw**bG!+OL(^ z5~}@^z;U1prb#&CBTUHv%=B44eDNbl!ce2?9P~(dwE-qIiwHWJZ+HV(Xx#e9J5M?<%`LAh6!;V$?~wVNHL`G+I8Ul zj00B&CC@XtsslCz(ePtP=#9}$Z{&{iTyQ3gGYvEmMlaIglf;?y(L`d3&v4YeMp6O} z_?SbCNlsuiDey4UMADg3*X~CXtn=1D5$5Xpp(16110&}g?J|JWY-A*JFxT}C2TwTW65(l>YxID5o(nF( zIKf>LAceOO`-*#`_A>JQIEIB?=^9rT51hbUCP!xvt6{Jns4Md=yp9BuiFn4k&Jfj= z!_z2IW$U?NWi3(|$W*CKO#|Ed^l49t)Ivl;FEBeGt&FIMB(Ur@x0z<27s6Gj1QJplmH^l<4IFih*_%4BhO&`fcFvmGiPzcaL z<36|Jph-zt7ETyR${6IndR_gK(+gX#e0@+tDFip0Zns!2?e zb0&(~%jf6?o9qtIww>dV z7Q5`^2();X`xa#Z4l<6?DBp4;07tK!Ui2s`>IoUI+||qM+RIpe#x0eah%j`a%MM{{ zzYb1Biab<2ooZi+1`dR0aK`63j9Dpd`Llg-s{Ers^&Te#SN!w>RyyE}CkHgagBd61 zT$d;3_We^~Uv)yrx=jw%Ws0kLnnd7TMDGetIDVDu^zP^xv&1=5JKEc zp3_I}hm1a7;Vfl$7Ltxb2&0EaTVDyNiboO-Rj5+!xB%nrGbWjt9PkXD1T{kCI!z>; z#R1eIMc`)qjc1`yNLq^&I!GNo%@R9!e}3UY!scEyikK8(_fPlY#`_E zxdyF}CMi6^9be)M=j_*&A>=%l^6iD!OCT>IAhUR*s)^$csOBtlyXrr>cP%bT;0rE* z(j*~Nk6~wni(U<^dtR+nSF>oy237${8jW8c_OIJxYYs`6ptY}bxwEY{$n8NCRup(y ze_C8@x7=4mbnHMnm`=b5stl%Y4kV{Ciq@s+d4>_M-F9%mC32Nt$4`>`E<yua42ji-h6IptIkrvU-6^WL63EjpFhyZ26`zXeWx zeM6g^I3&z9KlgJ#_u<|9AO7e^-~You>P2@F(*<7z#3^?D@4VP?)K2Y_ zX!?Xb!Rs1w<@w>=_032B?l1j!zw$r)oo_yPd6YizYV=f!k=}!p05;5y5W9_DLmuNZ z5N7!Ne9rB~&h(jDXr2W={Rj}d`VFg0gUvVJeEaSF`<(N=@BQxQ=ksz{Ol;H9Bk1LX zW!IYg9Lvv~)Qlk&W(m{$XrA>K8HORmjC1;=Hlxn{j6OC{?jy&0QLX;cnhE0ixlIQ- z%ruNUG>%yVu9x3OW~4D_pi%D59Z`(Sn5cs(INsr=%U{ zZRQ&O>Hqdme*M>fOXy+)9E`n4(SajQ;NQZ zxtfol0c~s;Z-Eh~1SU`u?D-Qk$<&%-FBLEshG0RMiw%K+7Kj_rnqRCzPTjL8O3~F* zSZ*BvF0S#pk0?I@og`fnFZ0{2L(Mib$$@b6>wF>2)8v3Hm^<`l7Z<~rIJlOQffM_4~4WUc{|%(VsH zi-P0z#XV0w3g8;EcT;-et_a_cj z@lVoqQDCgwuywGXN?5Vz~OOU{eNGZ^sgQu7L(c!(rx{$LWgLxLUw2 z5+%uVJ@Ll(4mj-S8_J+sJL)<2^&7x;3wW33#k+{q^z8zwdS!Oad-HkuWAO~@4tnm# ziS1LP$jqjT4xHi;;f|`SE9*&4@|elt(gpB^WBp>yQHjo%uwem#VwQC2waT3~0pOkvFx^*rcQ~{BbUe?7 zse?$bf?Q(jrb>{3`u= zX(yy7(h*6#sfevGCnV9C-4#lJE|}DBSEJz-q-&n`cORJ<+tu7g$64z_O`f#3BwB&u6)${z7tq z94f`4Iv2O2-f^J$XhP~FThFh(dh61COVUejhve~CId#!#zWr>ESKbm$N17088AqSz%%;`bOfOzlUn2I7k(rwYnx-NVEX`CWrxQ}FxCgIE z&N-P{p;Q^cP1GSc<_TYz{?A2qBH-SAbr6jg=R=S$lHn?}ewd<^O(Xroz8RRoFZ|9>&f17JxuEgtJewJJ7V@u-^v+;L^TpPR zhP3@@ps%G$DPxR`b2>doT3FKEt2Am|b+g8+XQYH~m`A4bZ)rRkCAGd5_q)Ve*=aoN zae+y)epg^Q$mQ5xF^B0lzR3>J2!LjG@$Vklav9jtxrEUxMg>N4y3Y`5mC9y~(7;hc&{a1c)n=;{yaVfT^sE0f!=aInkEuc)Y4@&`Du1cyq}527g#&zCEL_lTXquZ|Ug z89G%7amr*!ARD(sX{k;kc{cC+R6En}7!5q5t~|)D8G(}Z&l}Kl&NZI%3_f0U$Qo5O zQKA}-aijl;*Nf*g4j0Ibde|Vh!=8|<0lm!*GkCthjz-S&R4TE^&ln&oM(8)emnfa$ zt@&@YWN?=kg#{L43r-EB9VG4*QfKDY+>^|u>GZT%RZ-%(=BoQ{7O)r#eUH+!+5qd) zoO7sGR@M{lz?G&RLT6!F6NW~O?eUyaAx`6DgA=(IhQQWE`hc!*rZCiU8kuM6CMmJo zeHBp|5UX#K!bJ;^y|<_d>C4G+E; zVy14m2i2t|uMX3@v*`6!d$UqcQ5?_VdfYc5Fchq@6RpN59b71q^BSuIi~}U(wqQ%d z(IiQ52+E+)jiCXjWtB!~crWpmhZh&X(Aj!l;%K^H)Q~=&)5$^=%v^JlYp#=ibHo7c z)*Uy%bv5z>r1PHUU&qwpd%3XhxFc#!tl^xe+QO_?7C7L**}#?4jW0W2)53^NM(u z4%B0c%5o*KwI+ZjB&LqPUQzKK%LUh>P`A|FwGU7ZK}Lye&%m*AI=4wAWud9x#Qtnw zBc#uYNyEq_sZ-e_0rQRS61KCP83^*C8@qESBEIhZ5fdOM|YM*kR=+gGj*BB>s7tY zdNz=R%7acNXG=L_(G=68O(7i<*ocmlLaUNngj)a_SP}m?F()OenQXp5WR-;0J5g^qD1mg zN|iB=)y1{J+9Ex|rl6p6>tW&j+^Z>=>~4T8|4zs%QBBVodFJ5u6YekOfaD~(>ET?H z11B^7yFdH0@83TB@cE-Z{G;zb&jABTus*`kgp&{`a{aRi} z`SojcXGc8|U>xW2arvwNNCdZtsrzIYwmuF5xfddF>!IiUmR;;H z1Me<>?9=-{{e$28rT_N7{@;J)fBy8A2S6{W6q@Mq9G>S|xEu|~Avs@%6irg_iX;FBGFKDPbpQEKl*+r2nT#Pxvt8T89 zRtGVgNvWLK0w60BSddyA=R|s$a0U{ZXp%na?3{CLz`23xI2>s*QN3$qnbXMD0CUc% z7D)zgR~K?NT?PZ-L}STm60I(S8Wm) z6?6I&xJiy1bK9JIWa=ks0W#Aq&-xPoob#j>NT0PJb!hWa6(Cc(Q6mDLkyBl@vrvaw zF+HbgD=_1bmN@qOc5B0yq~~Y{k3#vJq>4la`;a=4HCek!1~QsJ)@uNqgq(!>LVfXm z1UXcFtT_xb#}-(k4{G#J;did+5Pu<2Dza4i1;ZnsTLBM!<^1PJ~nIwth1D;sD#@<^g*G$ecIfLUcNoT)$PHoje z`kI;Y6@nXXWKmgr5JruR2Zu;L7f(YKi-NODu9wnH?3xqt#F=ZFD63+^E5%CJEPz*> z0UdC8pWWOByo{`xJXy2Ed4%dSazZ$h^sON?CUkEHOAm@+a%QgcJToe(?iLz59k2pu zhr>$7JT@Wr)tJkT;UVKxY(uhsQPo>;#X3A4(=kbM)K!!sQWuP@F?&*jbReDOQM2k+ z8r8Af%9Deb%yoaCF|zaGG0&N9ZkRo*Ojo1Wx0AYjZ8h}u=@+vf(`HCb2H9FpeUz_5c6hjVV%0$jAj_rAlf?M90R{Mp_*YCHe!NAuWiuJu;pI9jzZ91P9=(T{`=w z^v@j=U9E4JAxTCgm{VF907kW`IocqTP*erME4HjTX~50)9}sHanO>#dCG5MOn@+m^ z*iF)CA`ttzx@3-Hh(pX?O$oec94Rfj7+o3esoZUr!06SW zqJX2CXfQmHCb>}}pqCYy*}J1}IQnlAE~m$DlPfXEg?)~u4k_QTrd(3T?CiKA^*^1H zjXj7Ap=hKWL2$Yo#%7!Z!w?*ytOz;F!Gd6mpKrXPUVYJG(+2#wSYcJ^G ztcO?bu~wM#>|j*i8ek^RXydlCpxO9UI-pKXQ@}$S?(Ei>(b@puB%n6vd-LYdtB z??t$DTHaKzDPo=A@1aBA1A9Vb(I1tPR;8Rw9YYqpM^A?JzL zvACz!yROOevl)zBSKY9HgBl8>Bnw5J^q@{Rx7BIx{E~Pl?<$+NcXhlEHe#R_obtAi z-DsDqMtN=8sFA~QW*QlwZN{-eO6e>HYumULDYpKk!Xax%O-q24HY%Y%qEo2RJE0== zfptMRPnRZm31^tWKxA%!kLrB}wc({|u>Pf<^ z_h%9kVde_YdP(3h(~H%N9D&nD9jHj12JB?KfSeHZ3R6(vVV5S=iBB&-rx!)mq=Hso zO&>s;POnYVk7Jr%Qz=2}iw{Bu}E# z=+`QhCQ1@U&Kaqv4jN+v^3xsuHNkA5ajw$ShA(@>6N@!mp-WSemd?8XSP(>aCh zObHA^x@GEu*rF?i<9W_>UgkM@Hk}V8K|4NxXr4o!HQ&H#;Dsc0Mt0zY5=PRzqXGh& zoW}P=mv5P=DF1 zNg#a&UOAYN>zp4IMOAdK9#*4viUu-VLU^)LO~4s zww$g5Y&_3t;RIGepxo~1P~dbwT@768NZ1fNt#Z>oXgv%-Zz?ieb3<}64p4qBQXTvL z$7#iT96GX0R)B%H>i}^kIh8o)5E_TZDtUBT7okb3&h00hBV*TLNY32xq@%4y7pxJ88w zUtGK;lRen3k&}mhD^jOC^srTFM04e(D!PUAJ<%4&j)c%+ENKO{sct}DZUfs{dpcRUtv}p8k^YD{Vhky*D@|i;#R`;QODSyZ3oT*?v1q3QBX#o?kgu%v&NH&*xK9gY)8Qdd>hD z>Du;AT{o%?>de96{bPRfpZ_2K@E`rJfBL6C_=%tRi+}N(Z-4v!1_P5^gHs_E3<^S{d{>Q)bOMm-s{fpoFKfnFnUC58%$zFm@B1prD=?!N? ztpq&Jv(5($DdM+PO06Skea8eZPY(H9Lk2E6xg*S$<9z3vZ{ELu|HB{t=)2$jt}=6p z$}GLm9dFG}M{bvNE&{Tqb{FwQXb`aHICN;^Os7j~Rd{)NRv{dqm)m#4NBb!of#7w` zNx*dNmB0aM0RSPGic9nLdSMk1>QeTV17n_Pro0iDZ2O*988#DXXHBkP(axjV5c(8H zG8<8;V5}OqJW)*HT2SP)6hltkylVC&+k0fNI@~Y{Y5$3xAF1KUR4U}=fVJ>75{_Qe zeBIs@W;_%TEF>} z<~t@j<9-B*dOKXlHt3j~1e5VPbh?sJW;8EUR%n4f_jpxEolabv5n7f&>sXF8m$E)m z`jjVLSGPf=QV|FYt^4IwQ7sXm>P*hfw_wpM={GJPVMSZQLbXl;*L1B(h+T{Tn!8<1 zjfZezTM7UYPczHDr`5;n#KHCXTf8j+Lg*Bzg}UTca4I{LCt>GOmFI>lYsCs9c+R=& zz>3(ZW|gz@RhmP|0E!2q?Y(C&hb5RBf({19dXC-qI#I)+B>=yt`-&P#L z)=}7{!^WT3br-#Dnog2r7ipjuQLL9W90v*`fjc;?Tv{z5UqV@t$+k;Ppn#kC+b1cc zHtqos!)YJM##v-`6@IjQ|0+x-YECNBO54vYn8l=)&FgQ)F2625aDvz=^hs}BZ4aQe zRCq~iur>4D=>Ao`D!^A}qJ<49V^$Y#e42VvTZc>)v`ww8+lsm?ji(#M;>ZQr-xV&Z`&7N7&} zvht0|qPy}yQV0JE^mezYmUPj>?Wb9uvXHgJ%*xYv?rkD1m}|K`3QQ-AhM3nUsQ`5V z*Vm^1w-Vdsfhu`-24E+0YT36;7)*BhA7%ZzfUgykgmjB`hzm_So0I5NiDh};;Ojwy zg;LApO*gkY)7mm6MsHjIx=#5p2*`q7=V;q26PUjt!1WL?Rb z)cNj4>F;!|vn?m0k=bCte~!2dxR{(1$Ktjl!8KPIG9~P7Ah9zz zNY_1{Cy0ZG<6y0Va7OJ0};AUZSIg@&4T z9Er9VY-4+hbw(fo?9c$*WmTvy{dv7A<9?KdEdtn<6%ocsw_=1y1NCVFI+e#!5--c- zuII=(SNUdg%nD$|WV4C2MOp z-d3tt_w|dr9$V(D?J#6-u1)5;R(XlZA)FRijUjXO!Ddx6dr9`I6;wn+CNdS2BpJsk zkA`F)U<2exjoxeJMI~QTqED*JdZ+dt_XaLh6UkO?s;rDPu6MX708!SU_YjeZcD^aP z9}%5r4#9G!$eels-EVhY*B4pue@jDHi(GS|dsAXT`qDlavw>5!3puZQ8JNzidl{oJKj#!Lbqnii(|8w$GX&Gd7ZO z*WrOnbKRo{hm9NJHIWE=F%Na~=}OpoMaFt=Q@|IAUr{847A->A5Gpa8WClZsOQ8m= z#u))axu!$NZHuCE_jn|1c@O_TTW`~AU6-9_UDsIWsSlU!N)QO!Wh=-i!4~O(5CmF@ zk~Zlf(uFQ`ff|Q`LBbSMds_d$I{q8kx8uwiLyq4Hi z?{m)Cd#*Xh$2~shTx(O8((9{)m4=e&)NT#|$)lLUw2C?P=_b@6Z0akISZmRn4(>WS zoXtyA^7b1<$7?tC&ZR6T0XvDx2s68Pv2l&ztO6%&3O4x<2eQZgmFcn6hYAP~=kf>* zpskheNcK|OdBE00CA`O^48a=?(zgUKP@pNN41Z>G)}xj-IJaysJWm=k;e!C z*uX@BDN{jgX6PV+3isw5TzPXBfO__;O5gryLX?!k&exoy)@6kzm>;Pp*-j_N$YEl-jXB~Z0210tY`fbyXTC#4h><; z`LxVe&u56qdCQTUNdS_{C!|(>!~tQixys0t`=-f;$3Fm5QYoor;Yh8)nc7rmc_?ge zhYigO%V}riXs&*R9CFnT`xLb&zRt5SkGUYofTz)TpwbntWmCdX+v^<=R4o=*MIG|{@@RPbaCl{ct;jIn_Smd4}Yf6 z`&_uoh9X0sjbXA|RV;C3;MKb=Yj|gV zNm6c~b&sjhSD`S;?ab`jb5dckWaK>Bk#>JFb2iS8gR2@yWyDkGNPS@*T^{aANq@jB zE3IIw-7CqEB*#O)$CutlmuzZ6>@aDpJ#wpARyx*0bfcf@4c;n)gMvFbjgM_M3CVR` z+x@M2W9K~jTL_MEcX#|ql6vICnUGdo#60IWJ@Pzata@uagldSxFb|P!S(*eaXQ&$^EN6Or9Irqn&X zn}!nWdQjR?1v!l5QDEZC-p^^KXWb(k2rbj_07d|)V|qR~29u;0f0Dgv2PGTFJ%Qu} zOeVOLufndCdKVuKs!ZWsa?kQQ_6+ZdY1QrB#B769?zAb;d+~eqQR89X^2cFu?@gu; zNMw^VaAsdX&+rKholObsy>lv!w^)*vKfP`1&%G+itgAD)giNyajRC`4eUxh;*}a}Z zOGjpRu>_a^sTixbq?ub4mfgHwfA*jL?Z5q(e*G`~>Cb=m&;R-V{j;xrF4w(VI#XpE zHVdQ(%I@BRAftDpYAfBeUP_y@oLhrj>BpMUiy z-~0CUt@$vmGec6ZFxTAsqUl}<0*L4Mo|^WXMw)qa^-b(d5^iChYBLLI8gaNukcET` zZXDR$T-Wt0zxu1c_=~^z(?9vi@BZ#T+~Jyfn|d8g!n#sGSSqwGXt{sdd^b6#QVtU0!-$*a?Ep-iZoeG=HxBesaH9f?_@XU zJ`f&J<2E~_j;@Auku-Ua8_mrlIpn)#?kKu~48cdzR@Pf+vHcEIa}`lw_=#K@AL z{Osqy@teQ-U;o{|`&a+{fA6P1`}zy>KmX3}{LbJ1pT2#~cRxN9V@P&|O-SPA&P8_b z100gNhcLT=hRe{Fd&%2cPybFbGj%lq4-gkTrTaUWCh-{Fr*4E^ei+zy(9e|^evLWW2?+2aCl2U+SU@u9JPv8Ln zgbau`i1fyuHy?-HJ$XJ>{!*GNh_m%D>g!Zs4mNSvhX~-x0C!`P{fYz^&MXcw(UbhrJUU7Jn?9klYzN024c2h=+61|t1gtMZ-RaOWkgD~a z1`kB3SSr16ppDS}G9*|NY-^yI7z6^neW_XO1HBm3YICVP+}t(`ha>?LpY7OCBnC+B zWrL&{E+pCWO<}DfdzuG=aOMGuc-*urW$(Fv62$=9EOMjlSnl3SL!M->har*;R^cgg zz(Z%HMn&(q_YQ3c)5;);Rbm6z>rnOM z*)Hlu0DA7rQ$g-qdwU7a2c$XJ-uGN@F^&+ns&cN}FP(UDo+O9ss`zA9yw^<;l2TPJ zoT}Sv=H3-D7ckrjq;4ncya7reuWP2QM33GMMIkhW zo)h2S$g7lfHW@^=RVN^R!l?8UNRI!BQCS(orjD`%Xub9F8mHw1jmx*de30cdANA}% zL9rJ;ao%tFEj>BgRwSC&d3@2~181^(a_{D-)-pl$@Am&o~ z{`P)OZkD7?krkS6w@0;GKa2w7bTrlxfUq;^t$hJ=!ID&Q0DGS52N+0}kB_V0R++Of zNEk#-+SMnIa|vUg!X1;4gu=UYo|XFHIPvq%Vd6sYBH$*tI-G>0jsAl35rl8=ZD&#p zE8qa+Mw8L4g+`O<$!;>bNp_ItQBJ#Mkk+FbQ#M^6xF2 zlV4p6xNwY-%v=aD^csz_>&)DzE{q2UG{Nk9N&WUcn$Os1+l;!2r$luFCv=&-l{pJ- za@!{Zo%vlbEwfzpwC-w;saW<-uIo#Y0GQQg*p)**24H#o-e^XME9~v4^`~BQYENJH z>$;?OQ<%JmTBH$9yEra_`-yN9B(|z`gIW zOhkRkFgfHBUTPy9>^8|%J=~^P$nifu+?CvB1;Q<|I63#YKXPa|KPjlYd)eG$0httF zU_dtaaKWUJk{6(p1R6$HYI-IBpDFHkQEIFtCyh=;RwD|OzPj|;y~ly2S3q1M!IKXF zPghLX=sXOvd#ZK0g?ppSeA&Hq!(%UvYg{nR)9&Pach6HFHKtqtIp(t(+~ipAu$wX| z9K&|5Drh~n!+{fkd796zk~D^9v#D>q_vlpX4<4>7j6jWhv>X!gi2Dg9N;2AyW@k2-6cgx*B7G~qIP>uEUqXgknwmeo=3;Beh}JJn-u@Wja6 zNoq+?e{z5wcA2eugLmaK`&cJ%7^CtB5|rjpM~1#?5XSb*cpS zj@MW*cAMaXNwVGxS0->r$Jt7B74{0D$M$ODlk5#20B+5#s7P|^eFzw4eAlg;NQ1$S z$89yvdeY|%h8xl^yXc&NbRJu_hpqRRnf_sygan$1fDl?=LqPK62}1ua{j>6pq;g~eapu}| zk7<%LR+>quALwSsT)P``Nn|TR<%HcRTi?y8U)=fuZfv|LC6G}6(9H7fBN9}2E+l!)HGALR|Ni&=^&k8@Kl;&+ zCLeX@+Do1Q(pSLa0#}k;$TS!1(u?DfO2wU2@WFAS)-a>h)Me3%Cp%3Wc4GDiG~zhC3XW9ZtytM z3UC9?ZXLHLy%6}4IbdzQo$5->` z0&|4@EZQN}=MlSV<;DYldf0+wH*?uu>(Mzx`K#`tzUtiy!{pPk#IpovoZw187y7dsk=g&M#0)X)NEzFXT?$ z-hHpn1K*I`HF)|ph?{x?lg4=OSM8l7#yWr0Gy=1%Y@?Rt_WGU0@ry48c?_}wW|{bq1TIfbVn(_quSHAlT-~Ho1`lJ8tAN`~G_#m)5bNLu8iO$Z+ z79Qab(AN5WH;Y|0W0F~HVPqwj_Q04B8aH~7=-{M$-MOYk?0XZ)Y{=;${yAL1{o3m} zb91yXLUxaTg9zE!_i_xPcfNaB)4uoKlD%16H@RPp?}nJY?{@ysoQiulVbNJ%h41Db5=d}{O|3qUJq9~N zVGzP8d+HB>=xm>K_kL9`oo52yOvdgo8i>!{|D5`fB&nm<3McgzkGcS4xcYp^H1I0$s3EbLpibSm$&ffnNObH*;Mu(p zv2yCO_lHTSEhsh%jm)Qh&xU30$|XVW>*IRzL&?p2u+c|LaN-D{_w#VeO?h;BR%JA7 zJ+P2e6f?8o=#>q~Y&VdFJ>8|gI-Y((k9OAC?7@$9IItj0#N4)*tDcD5Lvo2N`z8lO zgjBHsM;|=w_Fh12#?S#u;tWX(j%sY$v>NyZ{efveX?I7GR1TSD06x@uLi6wgD9tHqe$Wgy!;>()7pS6^Y~ z>S29O9vGO`XY$zsCM3C?Yi7B@;ktLP9%+lTmV)4BuSuF`U;r|HWLEM(nvU)TbIecm>JKrvm<8al6<}QbzSV;9ixgH zYLDLBz(wUYklvfR-q*e_sk@F z@5(hZr&8E$8g%$2z_bvq+q)_7j*v)y?(X$$sXJzu)~caJD+mLzIVpoAK=b6z54Sd> z0hC>@izM4<0IamX8ochk$z?0rVK*Iqhq0yund%p~iKHj^bdnusPoKa{H@!@rdZ6^o z$_Pm^2(RwHl0hzak6rC5?8Jw=3(U1yk>{0Ut|aLM{iv#kb=(_u>C+Pjs88l4u(O}< zyFj+G6`b+D@76o#fL-pf^e;{zsa>Fu<$yMc;&HtK)Ix{Z&0~=hVw&fV&hby(ORoI7 zjR0`N6i-P=>z!4?^(?zp7X+iZcR#K#_ip0M$UcnHUP0@T9vC9052<8F#bPf>{YCcP z>*I27f^(70H9|L8-Bfxxa;ZNl{vFtCyX-Z&x=lkGrG;DYZ2;j_wFYE1ubE+QfVp6~ z=ZU|ym(V%w0h&Mq&Cca+HtDg}x~=zXcdyGtMddhGDnMM8L_$ zAxQ(U*+`fqAJ^RXE0kU(fi$hqyN5NKI{{loUF^=eH_|d#GVVC~Y}`(Wvn0tS+%9r) zWpOsi-B@`LbA?nH+Y^98lG&*bUS7rF1Vx{|R*wA=Axi5tHXE<;hD|;o38#)j4ViFK z7w!ckK4D@uo1UDlJ_=7Gn;rr=jgT?PrP(w_Y;T9-o|&v9J#=;A3pd1bnBayJFMwdZ zn8$CO#2*-wq<)B8O6$bj^%w%(z$!R9pR(C=AnCN<-K6DC&a;qS)429rwI{t^;8<6k zefmbtBMHTSu*Nm&%9zwZxz@LNCnQJ+A->e#Q_WyL&kWh9l#`TmUyfty^{++XjE4L?N$4`4xd z=epjlD3sUjOdH?N2}DDvQqTKWcmw}6x=ePbuqM~c?#Zmlqh2%PzIN0~&NjuO+S7l0&Z*C7^|P+Xl?WE$Pm1_9#k&c~Gn zmXoC)HwqMQjd4S{3zLyu6V#9rcvkZUS{q72WE>iNY9qX<()+%leHMH)pOJf;+!x0o zs?0f~)J`oYl+AGWyjy!8#VSY6G~MjliQ-%GhZvRZMvgU6IpimD&GhcKPjOl%WEZ%+ zcDv2?G|j7}fHu<4RedoYLvp9=$#J7(9BY#jWtVWufN2*7GA&9aCg&GK+;d*I{>!u( zH{?D(yi(%Gm8!eHRCMcJbLGC1(~z=W-{^41@o*MHj}MZK@F?)%T+=c$nQ3E!mAPhL zb(EG)2vx_Hj0IR{=FTG7s`1R+tE9!N$$e<5%dvIp;gRTE$%$T?RBm3W%aNu**&7B2 zE_z2`Zbnz~Nh_A1&fkTiO@p4S^enD0)8_$vlAVf=dCcwPf)9tOEubPrnH zk8KD7gq}NWF`ra=_IAC1q#8W}NBGvazWnO1{F}e<3*Y$@c%Z1qjW;=dE18wg1Lw9uo!Pytqo26-StJWz`dAtlqEq!l6Jcjtj?*sAPZ*=zl z`AAI(_)UE7}-VRImh``k%3*EPGnhkN?rXt-vEErl?S6YLrTac*|b zS84d}_k4i3GvPjdS|~K5``~wtKYeCX5C6 zYj>{UwR6o)T4V2?v}?06$Lo^9=9=j@eO8SgoR8Z3y%Muw-k52g58Y8ruc3)(H8M&_ zh;=iO33yytCTg6+L&^L z|F%{`Z>j%07)Scw^{6zvX?1)^s32@hVhmw*Nb^jh+Ii@@aY$PrG#2A9FU* zaOn2riCjZ^D|eDI*2|xQRM|ZGHSx~#AlWpIFQ5~$*|}yy)k&rIUTXt*<~OvAYTT*1 zPyK^wL|{R_A=sJex(}`@)Qtz96?<|{r64g-t>~pW?Ip;*QPiZV3-nnFd*%|-m`D_s z6-!aT=@>V+YB1ie*e84KJcZfAhMbr9AP9)L>SjVSj@f?6S2%7d&y`8pRGD_4OB0UV zNY=6R5)ff!CRssdlNnq@E9?t5S-fV*lHe7X zY)Ix3yYU&P8cc&PkOe|>aySl8aw~Plr5i&xaPTq2%DFKBY6EY|pI1MvRc80Hf(#t5 z3#0_%lVp>6fbJ#$pZ+bW)KdQNn%jUE)*bl>3AX9#g6Fyo)qCIWR_F{{7N9BJcA}DrdwLY`Zc9IK zN(frzOh|ciy1joV$b^y{Y8GZ-BPK+2E=;in1W0sBLpGMNa@KmGw4H6v;sC-PjK`+8 zN0;rGEL%G12S79D0HJwpI2Na`>`ZA3pfpc7YxPe=pRUfuA@cm&TI5kye~pg2nMro$ zavw41wpI|rV8Twr`F+qcRDYdr7<74om9LDgb7!&%dz~jOl}RK9#|1=^aK${EQ&$5A z$W+iRXbh2{hSg8T9)5N1SKw*1cawGJn%*COh(@bLoV|B<=8DuCI|?Ltl$rEDoGZL` zBUWI&B*%>;p*n}-BcOHRIIvHG9WWRgZ2%a~gQaPF*<5{M9Pj0ql*sbc%hs@K-CD9o zrT1;)xMNnh-&%wA5609T>2QDjj#Fv%VS=+@wo<3QWyKd6C-=*_uqMJuI0LzaB(zg6 zO#$zu*yuW5SH8aam~51Eo>U%I{_Uk7K{~rP@U5q4;<_H6P9+lG}w{5!Nrno@1heR`#joQhY7bNG5O9vnI9SV~mc184*04+sN zjhQ7wHyOQBPyPaTJ1ys4F^&OmO4$kpa-G-YRT!VY3aB1*#e%gxy;d>s5*Ou(erb5x z^1-2-GT|DnH_JA&O%lhNtqip%A&}d-HtSnJxt(*HV)Lw#8blCh@ZQ(Cg$$A%Cy7a@ zQV9s^ojq?+pwn2kd+vU*+CSYDk}blU(j;4eKiVuPJ2IlXgdKs?*EUlHz#5#{N-BD% zt(m0xaY|XIuS7zHR)5`iaRk=@2kP0U&IFtQZ|gV$$PpYy#*O1Xq9{Bd5zzqIE{kgW z$vr}JZQIVS06_ zWQN3orH?T6KFU0!F1z!>en1 ztO7@-UlMV^k58OEx{omj^8MeZ&xNuJU_Rl=ZEqy2ONfoq0Gr*9rQx%L_!CfRvt!x$-QLjwq3&-iVowucgjog9gY98p$=% zQqDj$f@Fxvhm1@zqPdbGqhrkLGEP3sz{Ot9uop~nO@>VGJ~5MAa+xs~{dX_C$gm@q zdl}7;YrJIGi;P?{I;7aWlw<~z&0=%C9i3umQXbP}ND^?TIDy@}qJRk4V@Ah?V*^L= zt$c_v4(Snig(Rt(p8ve2agE*m_P4+Ni@*52Z+-dlhky7l|L9-+(e>p+8s=G2on0w# z0P1O=WF=j~su>H7QPOp4<&WQ16P>fx^UnJZ&V_1o!f!ydELt0I9^-2Stnks$k2)BzkgkEn5vg&5 zW%wbOq1}I&lYn&Xk_LSuk$}^L5Cr7uZ8=oR>UR3!+UO6E-S|Mz+d&T-zw z!0w6We{SFiQ*{6;2fzmjz`yW@kkXFeoL@R6Z!`w9jzrn+AA$I?HP>qNwrARyMaYZqf!>h>)Z+St2O> zE3OAFg!aFh&m;8mE|QM#(!k}_sgc|#J4)9&JVNTV{3dW_PJSq)glMWmI#804<YdNh0naSQq(4Chf=eA+)M4u`{&xpPrrYf;c z^L*5W2B1qK-PLEDNwW#hVo;T^P((%3^9^L^=(!cS@C{jrUAu`F??d}>$ zIPs-&^76|`=ATub*PHP|aXg|pV?4=cS*tl0ZXKo6X(KFU!cBKCAN&MFO${NB;T%;s zS}mrc@_XN9vj7sIzqNCdiOvX3!Z!?c#!mQevLSbb&T+(YiqmqJA>nxjA0v1j=%A zmMA=|yn97qniF`tGNB|lQJ+K6WYgAh8K0T#jMHtQqn}Q%;8{~rTp=h#s2y9Wd2HWupW<54(;`X-ra|R8Cb4 zsTCT$W_s8)nt_zlqtj<5=g7)NE66#pKzn!Ab}Wh$g|{6I z9A$gWh&rCkHoM{oN>Un9!4|yYnA1OeU?tS6#XKh81UuROTk2{gV)NJHOj1_InLv?D zX<#1w<>>1@nCeE&?u;PB%IA%o<0p`5>Q%~{_9IXoJIDXzI2v%C+Te&mRo6#dhPR%o zyMEBiABZnU;PZ_!XUBY^*FY5OvN)D{3@^l7ThvaDy-+HP91%_4CU6{%@H~u$sh&Kv zb@u**z8MG{mis9ZmQ-wq_`7GF0oYo~?hJkAL{AUM^t`NNa#I09Mbi00ns@D#sWNsF z*1?)5qq`&R8}wf9I92^yyJG0v91wL&M&Zn#KHz~&ou`^kunZSh%sGtv%yV>sGc!v# z@S4v!(ut@OIC81-dnre94}eOn&efYpfz-G3xh?`3&nb*bEbqCx-kH(`9sJCYlTC6s zes;Z+%vJPh4uhTW^Eb#7bC1b?egNb7)PvS?>Xq(s1tikqFB-JQ#)YB!+Nmb1e z6=invXq#4mg#{iu%STn(OS=a@giCi9>t7CCr2j7z(vj8BS%&2~S=R4zR&{vyTC+ZLt6obe)4K=yo!E->ttFZ!8#zD zQGt(MYMxXUN${f7LHl=v)P93V!pV^e_6rvl{6BJQ1dFp3kX}_o?3Cyz>F4XFaQyP+AejndAV3Ni}g6YoI9NtyK52NP&?5w@t=U!{B zIj^f~)TqN%^ROz<3ydRN9WdK$Mxrp2?^ria0xTq$4)A(#i;dcu+q^Z(Fd#bwHcUb) zk&el_nmRNEUPDGiCUVuAG#=liO0K+wF5i`WATvB32k>>4mke`phI=K7W1rEa3yFE2 zUJ#Zl$b_uTJ9@34_nBs#1Fm_;CsjX$-m~ub4Lpdr6}h36Vl&BwB=7{aaitvNy-h!v zncw-H-}zI&`=_5@eEq|}|3`oF$A2<21kv#ugwq_((K>Ky=6>ne5RGr?0DO4w>{2jH zY8@tMuc@bkCV9L6e(QcC`SgzVb)a+{bNUJUiYSmoQ15;uZuOX?YrYP9Sw0XV<0RP1 zF3dGbp1O$WjT`~hN!BWwsDj3w-5IXXG>JfDE2(ncPnQ3_k$%KIn(W$6~@NLizFistQ4De4{iNIyABSWOd+1 zsM_?vTgsQbou#a5%xO3-mC{*5-`~t(;i7i`3k76-DtW)a7vq=?+O&5m^;89HO8>1x za>Bo@=|c;c>3^CoY+@Ma$B5M|9jD3cX!njk)x%WU$-TGn1vFXY3 zRu9hu z&ncv#(o0sNsxyr*(eWllY)J9F$s4)e95k&c&Q~@X^$Hh4u-rJQ0cX%?aIf`Qbc&vR z1fl|I7?Bt^re$CEZ*hNYo2RJs(XrlK(BJ}AYOv;eo)iXw;tYT@)2+2d`jtK@e-VnX zaFamS6kQ`zc{bYm2B6hok8OI_)cXSHSHO8f_Van3*{py=_!{0v*+~{V6r&`ZF%caS zKh`DJBPy!hEIMX?o;=xWs_>iy@#| zXdcb(PK{QX0C9q3`}oL8QBEp|FfwHxNRcRxx1Px1=XGqz>gj&Xme$q&8Sc)R5&lrQL;{mkkF*k-H1cNg-lE;Sc zL}3Y{1)8)jV~6y@g^5FZ6VI6{9W9hewiG>yJ1!}ir1WL3UY&`k;k0eM?qkVw?i@5( zvd&k!J6(Uoq(rRHP2%bnw2VfZMD~oFm4{4NvYVXsJ z4lYg~Eh>Q=Isk+|r2{wzV-Uv28L~-F2rCy3O2AXnQo|!UIv|NubS)?r|je z>fq2&3Q%L#6AU0{Scm3cx|KM@IyQ2ApnxKYYA86l%Dd`d*Le6iTVG=GLqc4A9~#*v{lO`z>f6cmZlJX;ZU$dl%~ zl`@z(=3}Ev^Y{!M1i)~2zJMjbqoc_#bgA4rr~*sErwH#Om#@p!IP-j2muRjD@ktNs z21Rx1!W}l>qZ0zm3=>J_>7(HjNan2qy9UX1u6expd^N{sznm^%5oV?6OFVMq~M^I*%#B9m} zYq67E%wz_fsEyJaMv=+X^wqk7!oWIT$wGq_paypQ#m1TR;YkF)Y7!Hf4@dX^wPBX{ptAp7-ot=vUpfCz4xcZX_7WkT?U`)|R0a=R>{uW{{~j zw&Vg}0NyxHOc6g+_#j$SovRkZv<{c}$#Q8eOnc^%eCY3ey}B>M2*uM7Ce$}!Ku}*yj(C`VX~(+-jZ;&N!Nz(({l6;z0F8 zLWiAwYF4rJZRkNjQWgX(nd=@^{#EVdWSqFYRsducmim`KhKJ(ghscNvcLTXY$fWDv z6?w-8HlMM6YU_=nM)2j#!(=~sSa$XqW?3q7^4Umj-)6n-=tGs|rDJIM*tuJs5VAK^Uor9kb%qO1Gx=nm zhs_@D(Lqwyaxy@PL;|JRZWCDNco9j$%y4SfD|)SQ4zkk-Wc2gt)q=#RxsS*GX=t0F zVo3UI;Te(w$cwgPsW#JR5UG4F>DF?S@H~@5oVob#GwJS<dLvZ*FmLV8jazYR=MH3oy_ArOO_kt6gXRti*l&NF~-HVi0p@m!HXoheVM?F?D% ztyd44@F+7~b|iI#vDHIfSd-`Z8md|yC-lQ7bN`s5pjdstFrqr?2Ph z@BQ!p$3Op<|DvQ)+B=4@QP0cOn*9e81a<0$+Q*Ugf82K9{1lCz7az}m00kMmxRn%7 zJXbc?P)6M*rsJ1NdtAK|ZhE-48mKgR<+h3Ae!mu9?fS8+8LdFy>XIzso8!rrrBqFj z-aJ8ADp`*{dh9$v4W#IqrL!K7l8iK;swbPpuse1;mH)n}h*Yo?F(|=MW-h;EbsVSs zp10mDmg9sxkogg&sDlS*LYX8Ne||jRF-vh4=IO;z3NrjCu_uX>MiYJdU~_rI#^uZ7 zS-f~yo1NYP80lJTXestTbUjIoQV2*ecT}xnrEPQ{ZSunh6 zFKliZ+}Up4BZh&eS-anWFxm{##n#Mt$DJ6j0K}j(c1|_7K2!Pj7gB!#4u(z2f+bUJ zH}lM=zo=46o>HhA=26^*96VDV0yXRlcMs&cBPwjiDUB;<)DY;+ix*xvel`x6;B}k| z2`bg(yB&HW$YBq1hLa+^o9D|^lDRhUbcmCpgQmYV){D6NJr_JmlYldm5x(cl-hD>`*_AxDS zCa)SKn><58qw>qo01X2340ItwtuV~)KF>oc_+^=De1C(wT~!I}%+%<0pQ3SRMzsnQ z5j~>5i_O{K8*iuX0B5!H_r!h^P7KD`#5*Q#&M_-`+JUqY2;#yWm??sRGRor}ZB#hVM?mxk61jT?>K zr3tKhe(@+Isx9IWtS=upAvhul0+RbinCa=gfo>i!RRv-x#EF3~FVSHmt{ME1#VrFN zMyH1B7?JN7NRVt^55mW6=6SO5P%Ua4d?)`qd{7-fcT;ubfUM@VqxPiD;W9j=R~o#- zQ3p{_M~hu~QAy@8@8^m!e))|nGG;%%e(S*Twq7xYduUc83_urOt&<1%E|B0y0t$wv zzW_7P_zAmPKagYUs}Ed#&htD;MyS8o-fjJbVUX=Hn+Ip=*8?PZ^>v)2>GZW);&l7j ztChg(mY1g_?+!^awjntkx$Xm~v{eMH2Q+omz+9fXJnM|O1gZQ5b}Y?f)eB&;e$E_l z^q;Q7?fz1I=T6dBq5f07Bun_k*OQ!J8!Jx@)X3UtuZ-{UKc=Xdp@nP5nf@lA$^L8U zNjIs(*)3Tw&L4kXex_k!JEM|ZouFI_emb)bA=$a-{aib*<5c`@8`FC25c2l5xDk)Z zknV*B9L68@)u&IN0;!ZQua+L|rN{0+352?Ub>p#m=B>$9D5&(g3*&2MfxyHb*#Szk z8fU@wiR|tdxl#-jm7F#?VLXYZ3AeAQTGGfF!b3&uKYhOq05io`kDox(C9uvAFK;4A zT|reLrwmv{wFOBG%x71OVD-Iq>27&({-TCWe&B!p(`ob*Mm7>^rx}MFN3m9-)ps z4kqMm;y&}nrSHm?95}%Toqh_II zXP!V$5YUOK{>YSs9DQZdd2r+m6}G74a|GhzbARE)B(=^BUzH^7GYHzOj5|%BGuR|D z2rNP@QCk;r9Hid=0^%k!h9y`A7vad;%s`s6s-Ht0M5%nQf6$h-nxqir+>buYAM|*R zut_r1e>z-KhPitk&p_McLk7Qw<0Gf1F1W2X!4vV)0-=1xb0RV z8-NVOzsp`Npd!lBbD1*_n65X;%FNu)csavnh80@RrXs!mQoO5Ft0>YazF9p}k8N3Q^_|1xWv>Fq%$%A@ zaOW!UgEiegh#_R&r(|KY=1#^ZEabb6s`1AOduCG5`F_aX!M=As@%BhEqjSHRJ9>R) z=*KpSA)N@~1-xzS3IVe#FKwGQkeDoe1mFlQ7vO&V1;ZY z1{8GAL1720m6v9^Lj!q@v_g%^MO9FFx1%hdX1lRMAxHw8WgV|QQgCChbv^e$EN1h}yFL2miX-t+=pFM|+co60HiYj>Kf2oi zNZx2ZBP7mHFP+kTTBA9yGvK#Vk$0H^Sh7Mog?zI!5g}T`h1jxbRCJh;e3tn1;f>;? zPT0}E^-W`TYx-(Rt7^fMjF+*sQgmu_uevI*jNq-FwnYrU4OqM9?Ej5$o4O{Md2Ze@ z`Bd@W3Oam?GyTbl86DE{E~GmIg&}%D5W1U6V5|6cz;2(8<2%o!-d-1Z1=hA>1_*S+ z;Z&ZKd(fE}sh7V4O$*@8$8Uer(@S0E89^ovk^+)>TAS)(mvaq8<1+ zNh*sErQM9sxPj_ZfpnlLc6F*j=G&q2(!h$<>yWO#5UrhN?8Cq@Bv@nN_|=D5-Mj;2 zc-K)E0)vp`fq~CLHu?Pg^MCYb|LpJn?yqJ4RXFFj2$GMQgm$$1cdC!NNDqpa>{&$ zXbGTMiS!3=97b3YZxI_phzU&AA)lWchQ!Bo@r@Hc3LRF?mJe;$g0VPTnd^77xV#e9 zfXwgz{_p+tJHPsT_V51PzkA)H(YAf79n+bfXG)tO(D;_oHq!^*ocK(C$(D&T(o!T! zN5jcHZDAMF&Lr&2^}+@@lL?ElkgGJln!x#Ki5PA56HDid`P7ytHc_SiQLof1W^(x@8J!NJd+3r1|kP0(z36hBs#t9^Aaai!eA|Se# zCC64J*TlI;Q(8S)fq4J`AOJ~3K~xKjA^;(}n@EO>{jLzE=vgKp32I$}$Hp!O?Cv6BZ4U|SVF2QQrB zhO0k;w60@)+>5L8a{&SBEC>4Seg=Sa+!Mul>)3QZCNxrxe1j%%R;2T>0M zdZaOlkE-^#`uAiLc}gt}mj?rlZYV_AWIV68QBDXX(sgJJnUDBP7h)|Mj6ieu7Q(I2 zK)@;fmbgt2r^GD7Y!|x2=JPmHlHpmT_r6QmcOiqk*w%3Q8S89i;dNv7vp;kpA^p&3 z++h8_WRek3&{S?4LQi z2>Ir^aGpPs<4@5KtvE|%I<}YJNsZeHCF644FhTsd%*;kK4#U=6yKEi^JI=(P9dnN_ zn1rT;D9KHX*kE9-%m|K?&SxvApehA?Q$zTApBe*aZ)Q~6Vq-8 zTHaN{nrQf@xbg;j$k5ypM1gqtI9al_EvaIgiZHr1ghpvWnXW^KbiHKrOJLnEdiha( zb?5PJ(QfoOPuUh}oi0fyZ@IIX!~5E3<1S{C$jmK6%D?JO@R{uH0Y_a=)MC}!)H1Ib z%BzCaz_av~kOIin4?*6mni3dtwr3D+{aG)rP3nU6u+B0|7{^xe1kSYpxIn4`q;59m z%SJ}c5vT)Lbl5oT%`-{PEJ5gM>pZnN5dlYfS?u;l zXC<`0?3~53{)(Jc0X9O?(D(k%G|A_HL^YB6L85$(y9-ylI3ycav)PgLK?c~iv#1n3 z10x|V!BfN=PY9LknO|goKBXOPid3gu%PIZZMSbfipkbtxWOwN5^+!SKn2R>kQC|r= zr(o}hdp8cN1f3bkC6jwe-i%Ta@-l7_&6hsd6Xa_b{lL&tai*4i^X92G)i=e7Xw66r+?Cz@^jH?RjUClx<@@!;0#f{OiUn9%!N z>-F{8IXE!5;{}8#BV>}S<|gC<@y-1SHECZ#o*?CI%_gCEKB?U~=r9$RIE3Cu_t`nHI^Lc{yVX=1Hx?NSStsK8#k2XakACZ){=TI&=$qs%lD}b6XZaZ$ z%^w4S65b@6DaFOy_Ko8wI8Pgp*bc_WTZVLJq*W7*?qfkHs1Rm26E&6ScyaU$*6Z~M z%Hp{B*-7!tetz*xSm#`SMvS(X5VHF4Mqp$fCS0z9>2(Rjxs^yeX3mpi1tfYY@ayUI zgC%xA5>cP7n@gm+lD&k;if@gjbEpV3;A9k4G*0_3Hp8DhY<*rOt0qwght#9J%tPKR z`&Rf2q~ilj81Hh=dUvIy|C;8O-8lZF&F1b2E z-C&(qcv(S!8n%7JopIC7X0HbCBlw7{3 zj||QP4b0@8wW+XlO|K3^pfpjZ-I(XG^;;nf7<878lCId2fy8k#`Rt;jVVQyuoe;P} zqCGxa7*TV-wPjM7E8=r2>s3LgYBfsK%dIMaWS(a~KRc0;dCmPWX15b4_6hWUjg$S% zu-R(K)xneO%=A*FOtl~H@`3{clVc3^y9vT4eOfALHucS9r;#ow+01lzDm|eT8vP{q zv1XjHkp#;3a-)XE_mZl9B)vBK9KBN%KxWyaZZ3R*&fs!v!XYo_p|LOtW1X&FI6Sq) z5ZDA~Ode-HVV==^67$StKR`%vOj5$-=Z|B+)fv?=wyt}nml|CyE;7Bxp8Hl=*<|X@ zCl_{i5gUqx9B)FYWBBQ(Up>F~dw=ko-~5J3LF3c~z#>+7#~ZYxNx4utYA#7eeWQgV z;evA0#y6s=ZaQF+(FF0exk}HYBzbz$6gpC1#<}U=`4BQq1mCK3 z!p7^Y*LoAku2l+q!xQR>@mud-9R+oqJ=|kMb)i#8)u}ki?#3o>yOIK}0NrQG)?nr6 z_SC7;PX$B_d7kbD;XJ95AQkf9jdY@U;a3{!q%~ZkoE>SfXw6|&rU2d>coqBc{0&JU z!D3Es{eSwO0ea+^WL|MjU~=C>pJIa~37IMV-b*5yAxZbBUf;9}Z#$;=9o+#suS#V} z0SjC#%qccW2s1V~?0%G)C)pY(En8o9@dEx%g~0Hpu(Eny1ut0Kx~9#bcl`ynsKRR0 zV)RB;O2i&XE+&W(RU9XDs?@$E2xR2s&r+)&7W7~O--hX|rU#vUa@$W;duqRX_b8x5 zu#yc2@_ST;#pL?{nHw;HBM^Ei_16DCc29@ylqN5Y;hZYh#Xv{9E-XBn+YT^7qvE?m zGEXl7tBlYBb`UkU8S^O+R+oCZ<7Pvvip}u(`SS;V@NfNFfADV$|KeZ% zt3UZ?zagHF|8UY^3uSbQpH;qpfGSuye~TlpQxqqJYy;F4wJ%5_I2~WlAoHR+K5J%r z&NTvQ{R>Bg?HCxC&@&H@|y_Gmxrr$s0G)zAV>w#^-~#M9 zU1(O;p+`a>yTAP9FMs`ezxUHGfBBpL_c#CN-}`$r2GS?l7Q&H4WJu03XND5{Hl$je zcL3IisQ4n&0ER$$zdV~7^wx4L&U9n>;7BAv((P0Gz6yFOXsPuhpnBT{PucYQ6Km#9 z5CTWt+y92_k}t&i5on3H(4|hbvoa<7JWn94TWWqK$0hAr!|~ayFCPQX^cNx_HS;}T z(2!iv-ULnLUEL}|suzahU%gFu#*l2VoSEa{>1$UkshG!-#+XBf_NWifx?XRV`XcL0 zFDM0Q0?AJwE7MZ!J}XRD>CggW*kuur)5gDz8O5iQ@P;`>S94^hf{T z@BiT+{^7s(AN>1&=Fj|@-}&9&{~Le(zxYT0=>PgVf9HRBzP`R}amOXTH&@hP(Eg&^ zZQs`ArF6)})tkBd`Fe#@#up4JW!{}n=vD=@+@fxjq`DP>=`lryA_)~k6gKtl9;UX^ zv-6&V)@7eBNOB9Vj@Y5kbL(iO`~wU#M~H(-odcqJWkDL4ZZoxCuFK%Tl}cVcI&U-V zLjCLe3pg6f=^<}4XuITufn>{R61;FsuqK=OoflhbUqG^<(d-qwZ}N)JDFB&-Yz7O% z%#A7a({FS1vTlJ-c20E!H3WLpoG=4(UG8p4o6I`0%r^mUe=D<5w{eM0zS`IB`qEPN z0l!)RRES{xQJ(Tm)g*q$y~+USem~(XIYgSuw-=96=q?1op(Sa5r;4$?g%z>igz}j^S_$!T!in$^y7GFEIBQmYv!6tMSI50C*jpXYhxtxsNL_35 zQnurhnOo(d5kmEhDPHr-i&)=*8%Gjf*A2)7au3!E ziZZg*sU@EUW%Ql=d{jHifSj~|A`GzUQl@Mf04lb;FOMkT#lvqa4R#0k&I}eE(8t|C zF(jVul}d0XOImz%eFezArv|=fEypT;Rrv8o*Wa3cMn+E45R$@pajz4-b9I#O!^5HZ z>dYoY4%p5}l5ZIbAefLW=KZzNgA z86XMa6E?YsZ8t>MhKT{Wb36IW>m`Td!Ln0%zBd#w8pegIuZqyws(+-qsI^oIYA#iM zYxg~Cy0>t;=ju-bsY}niu$;J@2-ar# z!3nIYXOYghzAtid=CTk`Zp|J*TcC3~x75LO8&Ds)nZC8_XLi}f@6JWKza66D7mF9( zb})RN$*$g1jdBJWB>H!zrzSgj&o3Pzqg_3n_OVt#ovKON$3yIr%2shwhxO6m+s8<< ziRH3drxcIpdZOGN>Zbm}^YrF;X`la9iXJ{((6FP3_Vk)1QVz`yPLICNeA{=ESCVd= zBHPeW5*BK$%crJ@l4z3Ho?Gd7CJUn_)iToni9$M-e47up>13L0)wjd=`qbgn!@y?# zwqXltNc&FTJc^mv0XzMz4x^LLHlmKA*^YQLxmTy#8!p))Bh+)lnecW!*|0|W*Z%7o zP0en^N?jMI#D>(Qw-{@n=-ay4eM}y|jRx3D-Ds#dfM{C{lJd}k(BSpJ8G=BG%}JCu zh|4*3BXEeY^`stA`gcRu`}6f}(i1Shg-oL{_h_PIXU3&@uyzqb8MPgs#V%~`m3R9V zPVg-8t)TE-3{x^2?or|-6)AxkvN#C|PJR5*NnsB^uF|MMUX1tZcF|5Egs#fU9RZSl z$~*7WkmESFozxl)0D4Pgur{e89dQ#(@$w4A_TocF*lLk#Q3F9m>M&FpQ^)L<_p69I z$~|c^-$C?t5$5G?2)AGl>HsqjrT|+1g_^sDFeE`}CH6c@c6(`6?KK@==5@Dbv1AKv zOStdXtJN!H1snM-4W(eHQ_TJ>-(J&4QWEJq~Cm^M6BT}%(y18o+9KVT1V3%tn!sg zY>p36)a#(uk$Of1xJt=)d&l@jOHR&eV&Eo#z|7s$1+tQ z0*?vKMIWW}R*VBKP=829&aaMkz>QWVUt8d~J#z{2j(_6S+fp!ZUd}&*bDu%59%2?} zwg+{eQ?bCMhu6CfN#Q%bXkMU3$^CaYC_UDz#d^3No8`Wh$#?(7Z~1@F@`o4)#1C<* z7bzf%)ikC?y(<%IudXwvH6N&!mbKSiB#GP_tZ0-VD)H(bTS6Sqzkdgd3|Fr2$xgu)Fo4tq=!jQ-BAe){#VfBp5#U;N{L{Ez%LLKqj zr2u@@XwX&DYeI&T{~<|&4)%AP<<#lOXly&6pM)5^MuQ{e5)eklA^jaPQ{^qKiK4yh z=*GKIsR(xJ?nzF$3UzyjIb^231*X9W=F#-3|9b`K(yLENVd>yP-NC0N2f-}8@d0FN z)e1iBcP(tc-+Y}?v$W7kQFkOXp|0?iX@$8*Jt9vN~BnZ1x zFQ+RRAjI+v(wJXHT{3ET=eB=;V^zSa)|n0iFjT3-KDXaZ*7pHkh@x+*rs24qfdDfz z-$0CLfAd&9RXJ%hC>|@4tfQJ|WRs1LOF|W>pbw1*DARSL+_nyWJI^9y62#9fF@qvQ z@!!)7nhxOyA3)u~A`{a%_7Wc( z$`hZbhlLh`=RPVUHTZTp6QuFkH+qXnpkWy54na)^xi4p51xg4UumMYO-14DEr0gti z0h%`*p^fPw5#EK?ly0N0LoyX67`se(+|bt}&07eVtUjU$omfpGzUG^~lgF$?8qX^- zhWX+aq#+`)wa7S{ikVBHFFJLCk(g?m`^QNvKWiPJu?{*(FATjDe6;O;)N~&tlvmff zRO41D-Re#T#OU@>oX}O+(`gHb3MARaNmuZ!J%UVoZNO^%+CZ#*mvC{FB%ka&)|ZE0 z5P{uoj8eIWc_6V{4jnS1Q+0c3UZLqiyZ(rd+>=YE^m&b1O+=(5F9IDtFq1`0ATvBO zwb27)*u*Mmgz(e=&dg`-qFV?fUJPl1Hi6>YBFU>Y)B7UN&w=g!0OOY#&N~=%hDNWl zsp4>{z!c{?Cjn7xXy<;1`w^Ly)rm|bc}}+AVD1T#K=SC_)!`G>&%SAKR2=4fGCMbh zNqgml%9O#+z_-0B-^VkE*~I`sNSy{y40d3lR^#cc5JJYM zIR$GTQVF_iV*}*qDIhuowuYYAZ z2e#l_#mRe4Q5-P+v1A?WdZcm2nIy%{T6v62(Tjuc>D~z`(I?z2$;_aT;s$XNA`{}S zvS*iQymCTmzp>WDH~v%Gs6EL9#iC##vF651Zr3Lf3EKv`w|BlCg~D^2G?J$_GrmkXZpZWwwy$F0-nlPlF0KP>3Pq2j4Xfzj=nuq~I_nGr%*N zkSU&y1oLQ$UXu>}vM?U<;d&w*f)4#}%e$+U0}L)4A~u!GOcEF!t-t!V=4|=|ACxpcI&}d1tFqVK!wYgwp(;B> z@&SsKP18k1N)e#7mW&GOBp{Mn+o=wzPdM>6PXSxmke;w*K&2aUHzO`tLY;V@5AqiMQqh|g7jNmpr+&_#E` zEUB#_b})ZOxBh5+$_-jO&HI_Oz#lBBhZ5mr2Fj<8zEM>iXt3-b{J|eQfBZ*(>o5M| zFV11LyB0!~tvs+S>>wsT+nsw#y0076^O4tFQ;CvXIFJz1a6hXpQ7im)EsaTnjjH#b zQ##Fsv-xz^QTv5ab@SatZb>ww6qqKq=&>X>j?N|{foz`9)2Gc4^7*_w?BK1aI6Nxq z^m{>%GY4oVs|caADyZAZEWL|6rDekh#>pLZj;K~6AY7D z2qB}6+t*Gs9k)?OAr<$u3N_zgo9(gpn?z{C5FPVbrw@%~?l@@rwCHZDkSd8}cW%}m zawb-2wd&_ke`&Be9HI%u>IhM!uYCd886DF_7XCGOp?K18KnOEaM_%WVJ3kQW*?v$8 ztV2C}bNeB57nPKeFaDc?ugFMRy{7;ZWPA{OU0=Xt<~b9a<*^uEaYewSX#&uZ2HLl@ zv$$HTsUs>AW}e;rm{C7zs}zt;o>AFPDgbz%@8$-RG;dt{&#&Fw`9{Qf5@=_ighbFX zxr%i^AdqI8g8CDmEIMPd6`Ih5v+(P``s;u9@BO>a^W&NMZ~yGiKEJN@?2O5UA@Qbt z%zg6+BqY5714#1^SOw~CeFL_Mt{@6cPzsTYi-_P*?!c$&Z#!+0q z<5S0*Su>-V_A6kO>Zbq>Z)t9ld7k&n1fC>P$cjg^lTN=^HQ6{1eBQ1Z)V_y^2*ON{l@tHXFPI zc82EXc74Yu+ndMDMbs{!`j;PSvJ)eU0=a})0_&v8ZcPgrJy^h6>o+cv1qsz*f&#FIG;Qeps*rl4y&COSjwdg-A03ZNKL_t(&Xu7zOHvuy9B)3LT>AhH59eowKxIn#WQ;yiYAM(BY4kBvOw)E87QCAfkK2>J{n+9+7pz=c%f*H`<;1l47w}|;u zJwUw)r{jHS*R5j}?VeE+2*bd6q4{P-Ec1|d@HKyQcb#Y6FNSa4n=xq~x)lio@^s!Y z0x(~ryPEG~mb1f0M@8wc>ZO6FchoE%7XO+f^uk+7ysKaEB+JY+HHcV;bXYiQjuV(c z&9Zz%IvlqA)?Qw>+Q8{JqLxm)iBZ}T3oG`^9z^G}(Mk51=Vb-T=OzwG_1T^0p?T@6 zG;a2$rv)z%FkxNRmOud>ZwKBwqH0H&^66u4qUncsdNZamp=Y zPXRGY8v^a#H0)H#{ydZ5H0Xya#;>B+{UPq{rPegnbSUY6?CvvT)%phPPo&haq_&u1_CS_TkhFxd}pJ)zimILAt44yImq zYWKhK3-245z}<}LXWL{#9-p#inm0UoeW@5TG#btWqepK$UzlQlWvyyoo=M4oO=v>s z@qk8VyR;pD-Gw=I9rEUL?qek<(E&c2vr<}TD$ssRH&r+8g!<{A!d5Ip&L5tC=Lzt} z_1m4#3=1={11R<7yqgPw>_`3VN1d77D0Bd=&ep@!g|Wfgajp8F0Zk(abl^kWykS1m zW@lHj&hsYAvj`FfE-}A$_qf{ioGzIhL|d&;hZkpU#as zJaGDhw$3RnzXpbsZ=tV#?E^=A@k9s-RHrVK6~>p4r-*Jo&pb%>R?jR@P%K~BWOsOy zH0Cq865-}YB@i4jpn1sc`wIbio=)3Qa|>S;bo~k?ROffc zhk>vsB|z(N$E4FK3n|^-SU*AU;LD-Vevkj6J}KnY?yLrsurrU$XLp|Goe@q#>$lIY zPODipxqO%`(7se$6zB^9Bx(b;h990U1g$55V)ddxuf`yPm8F%XM{Qt zoAS!WTd$tWdMf5DGu$ty{Fr5%ROnf%y*1cP4BMhGd$i;CX&*x_P7*83fCn&J>cM z5;*9}=uL*BmRG+yPFg>d&zg(F-VmwEzHq*F29Z!ceL)ceCd1sl>Jqg(+ZP{w6ZNO# zQ76$t^7CCy0W3w&KCUnT>b1K^JG6aeTK$2yUOH(%vo1@_bpH3enPjGK1-_Np?(Rxr z3)HHSv8zL3xcr@qilc{qKKsmcSrQ+1XHJXTE%$Ytl8#(W7ufrTg|l@vd+5_9WIN#E zOUqimpDsg1JC32viHy@P61DlI6R2?GUBecv`;!7r^8kUd@zv-WXXYE%A%%UEjw8<` zkX8z%eF2d6TeZkoz*XxYB$V(qAGWc{$Pv{uTzXmIS`!3^zIz6LJA5%tLda5 zA*4wr&-7c4ti>h&SLsHk8ZMuyNrwRVYLPgQw51N*gp=!22t6gq-qK!)2R(IRL)`+V zOApDGg%uSD)FrkMB^@)G3zF74%Xl0Z{iYSAe2o}s(m59*T$Ea^_U9V(g;#-nQL@^0 z@Hld(%`!sM!JMM+PsVxeY6_}eFR~pAEBu9}lj@c=Z5uLUpQSkJZYn}w+ATkYPd}W0 z9U=3YREv^0^RjBFrPCp&g3gS*LDN5r(9BybqDuB_c3|c=j81G3n2fe$`L?bH&ymfwK6kMUNyy5IswM|eH;m*S9GgO(q`7N zsE;g3zYeLn`JP6~fTdp@T5lZl-DtxKTJc8tq;iU#WZ~6)-yj z{`imoME&dk{;&W0zxvBao$N&oI-C6RjYBJ=;6NjemKSeeUPiwVYrCF5<+9JbeCAfA z5jLhHL|Z0UZaYabqgw?KT4^=hM>v;kjGs1ttN(tk2NT z7HtIf!%Y3ILUuEaM+^jh%&>Jrp4#&3HqV45RTvtAMS>0)rrU<<1`i+P^sZ~n>O{M{e?Zg>Bm|HXgt>#x5)&r_lG zKjO6bhGPK0aW&|1@WVIzSn;8H2e~r1{l5*%Pg|EZzSM@;-CWpXJowwU-e{?flrb*9f zvWKP=y%x&FmKqWqK6H}tfxASM&j!0>d4j4NQnuuLLq45<26f{!^yHIY9VL+N`F*TQ z0o{z3D;wr$k_Vsn8L;;_L|btMIyrS}@3YQ4+rC9H0AC_@Dp1zxVI`um1gi_@DfP zzxs>6{N3~8ONAHCiXR~RfqA-#DvTC`^9zu?<#@-n(2SLZ(Wi!d5*b=1)m~P@Plgnz znS3r!&iJk!Qzp!iQ>;ft+_#+k`u=o#kp)toDoKv$6>&|D>Pdh)>o=_e9`)~FX!?iq zN2I7<<2>_RXQD&4$pmzaU^uz`-V<%k5c zY9$#bG!NwMQ0mcH4dhTL zaA4OgHoU~E$YzqD%y4kFPMVpc4GSbbK4jJXj!#ITB~K@B+SR8`o@d+@?@M;RNbBa; zdbHq_6*H53xI(d&!WL6%VIG2RU8)m)MPxS2JiSzL7Lr`>R)*bON6oLTb)d=4^RNZ9 zj6>?DYKV~5qa(Z3iZD+v=r|rPY)hn-2MNvU?e%32#KmmDW`!=pG)v5{91NkAmCazg z_n;pd^Gzb>)qdvf3EpHObV}AAmvB#=-BoLl^T{=P4h!w6@#zCU$gIM?%IJp~C};sh zZs(~!D+!Wvo3pne3klo6cl%Fbbn=^g97N3-&3F<%O*2++^o|oETCp1{nbZ-BjRqc9UiemQTc*|1-;LKiSV|f9D91hO10gT5S2{W@F zr*!B~;CR(m;+^T~qsmO5ISR7-boH$`m@1a;xlGh&EekudF$%{tOHiye*DE04!mW6yj&5)em9BpD zUEk$+I!wg=KoOu$wJl&e5A9xrR=ph^Aeou)3dx%?%UhB}pL#t-Rmp*-n{-Z{WG9+& z1fgjlqn`_YLh6FLZxg~aMO0sT;W#tCKVBO}df~%OpdvV4Sw1tsjH$XxkkP{@>E973 zVwd;SaT+|+TO>12HstkH7-rd-eu?T(?-Gygrlklo5BU}A{I~Aiu7`{>#lurL z@+{Db0CXJbVyKP+D5zpMvz&|Fia}dGIo%9bc!^}u`zD{D$OeD{|i5 zy18`^Hpi0rx*l?x)_oEZGZ{iRX?H>&_q}w3Phu` zG}YLDw-;dL57^8<+nlpb3MSBnA(#Se-~RQb!f2ui^V?m^HE#16otf-nv+&$dMVJ;} z`NWw=7aUCknl|E9e5M6P?!Q}>I~6|OvnQ>`I=yhRGi)-CPZ`^RV>9CidFGcH;*jdy z4iz67N5*@=F^z19&x7Vt2E`M(_!@KTU*G_RrTo7RWZ>#scISC|OSJ&vSC{?|519jC zbXeKpF^pZg{mk55&`0p)&-dmom}A z{))H~emv04z6zhYajnDnYfjD(i8Id8kiseXeuZeBnLs$-HHZ+MA|Ty8+XWG4KOY@upw7INbB^(KZ{FXKg;=c^Abe<6o4}NNV?EpM z%IA-2XI}NA*0iK68!K?pnh_E-3A$mrk*!d0#|^P%*x@#dT#yMgDQU7RI4TXG67_oN ztDY#lw9N*~HQFn>pZG|(7#1mjgq_Dj?VOp^FQBB7D@j{9sM+UHP$nP>N7%bc9NpUR14uhXRmpf2g>oAOyo zp>YP%;8DhJx#DnRL(vGYjkWh$oyO-~+?za@EKv5`5QsCkqx@L)MG4ZP(_?2TRlP%} z-)Ba{^NU+=|C})0jv51W>XOlLy;U7s3>2uBs*ch~q2qkMmMV0OqkqktzeJT?MdyZ-4v3tDgM3(jxgD@8GTGBC299wY6=JpGgkYtv1CXnMGb+=dIGdWo})wEo_2)95X?Z! z`6|u(b6p_6*`m}Sz>DX$6I4exG<)8mmXE%)EURYVR8K$gB|LnCxJdY3RsPaCqUqs` zYnbilHuksQFji67&*u++|NDRE-~PLwUq3U?zx4x}8P6Phfv-0yB2}mi`9}R- z>UcGu88m(Qyjknis58wZzX{Tu-4`0xyr&tUfpQU&{Qmd9|D*rmAN}DU{K5bCU;K;z z_dofkKYsk6!=4g#!lnk*lA?UpUXN-JtG`IoM03O)>91F@{@rmfsiy5G)n5o1N|vTq zesj-Al{&pg8Z+&nYUO?EHTWfBWC~pZ!Pw(SP`l z{?Y&Kpa1j!`p1tSEkEYX*1$v`kGTnr&`>_s2of8-=3hHbW*)+BZ0xHsCy>$xxCB&C(~bZS6*S49!x4z8ijt+_$N??^Z%nM}z?*X2+|8=j%R-s*%}do- z?CW+^ce}h&ORTas&uj@LjzU&Ql9JJvFS}KZL`ItqvEoS0wGArlEzj_q|$maP6uTu zY+ac6Mj(I)>h?Hm3}AdSlZTFm?e9pvi~k zsZ+H-R?k3b<)J3Y>0A{-0mG84Gf}8dzdk~d_DyWuw&72QNHPj3VHvh zo__tSfyT8T@^*Q

ns133{vaU|K(DwhG$rYzTKyo(d=kyvn!b$wA=GO}`{1q`PPg zQBuMHXl||KYk2=CF89*_4nWMg01lD%^QPkO_o`eN&=o4%Q!pU&7|iB*h9op3q=`3~ znNN0nZxT0W)Q_)jRbzCM}-6!2vAG78gfpS)K*9$L3#(UC_2*# zTMAGT@chV}S)4+uTo9Hpq(^J-GtcMRSC@>EPva%D`25mutABm

XE3lpQr4-&MIC4ICk9b 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%0DpL_GsM3a{wF@9vy7>hC_g= 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 wcLP!sG%VKmuI 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

); diff --git a/surfsense_web/components/homepage/features-bento-grid.tsx b/surfsense_web/components/homepage/features-bento-grid.tsx index 7406223de..49884bf28 100644 --- a/surfsense_web/components/homepage/features-bento-grid.tsx +++ b/surfsense_web/components/homepage/features-bento-grid.tsx @@ -1,5 +1,6 @@ import { IconBinaryTree, + IconBolt, IconMessage, IconMicrophone, IconSearch, @@ -709,6 +710,236 @@ const AiSortIllustration = () => (
); +const AutomationIllustration = () => ( +
+ + + AI automation flow illustration showing a trigger starting an AI agent that acts across + connectors + + + {/* Animated flow connectors */} + + + + + + + + + + + + + + {/* Trigger node */} + + + + Trigger + + {/* Schedule chip */} + + + + + + + Cron + + + {/* Event chip */} + + + + + Event + + + + + {/* AI Agent core */} + + + + AI Agent + + {/* Sparkle */} + + + + + + + + + + + {/* Actions across connectors */} + + + + Act on Connectors + + + + + + + + 25+ + + + + + {/* Sparkle accents */} + + + + + + + + + +
+); + const items = [ { title: "Find, Ask, Act", @@ -749,4 +980,12 @@ const items = [ className: "md:col-span-1", icon: , }, + { + title: "Automate Your Workflows", + description: + "Describe an AI agent in plain English and SurfSense builds it. Run it on a schedule or trigger it when a document lands, acting across all your connectors hands-free.", + header: , + className: "md:col-span-3", + icon: , + }, ]; diff --git a/surfsense_web/components/homepage/why-surfsense.tsx b/surfsense_web/components/homepage/why-surfsense.tsx index 8eeaf9874..38f19559b 100644 --- a/surfsense_web/components/homepage/why-surfsense.tsx +++ b/surfsense_web/components/homepage/why-surfsense.tsx @@ -348,6 +348,11 @@ const comparisonRows: { notebookLm: false, surfSense: true, }, + { + feature: "AI Automations & Agents", + notebookLm: false, + surfSense: "Scheduled & event-triggered", + }, { feature: "AI File Sorting", notebookLm: false, diff --git a/surfsense_web/components/new-chat/chat-example-prompts.tsx b/surfsense_web/components/new-chat/chat-example-prompts.tsx new file mode 100644 index 000000000..95d7a0eaa --- /dev/null +++ b/surfsense_web/components/new-chat/chat-example-prompts.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { CornerDownLeft, Lightbulb } from "lucide-react"; +import { memo, useCallback } from "react"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { CHAT_EXAMPLE_CATEGORIES } from "@/lib/chat/example-prompts"; + +interface ChatExamplePromptsProps { + /** Called with the chosen prompt text; the caller prefills the composer. */ + onSelect: (prompt: string) => void; +} + +const ExamplePromptButton = memo(function ExamplePromptButton({ + prompt, + onSelect, +}: { + prompt: string; + onSelect: (prompt: string) => void; +}) { + const handleClick = useCallback(() => onSelect(prompt), [prompt, onSelect]); + + return ( + + ); +}); + +export function ChatExamplePrompts({ onSelect }: ChatExamplePromptsProps) { + return ( +
+
+
+ +
+ + {CHAT_EXAMPLE_CATEGORIES.map((category) => ( + + {category.label} + + ))} + +
+ {CHAT_EXAMPLE_CATEGORIES.map((category) => ( + + +
    + {category.prompts.map((prompt) => ( +
  • + +
  • + ))} +
+
+
+ ))} +
+
+ ); +} diff --git a/surfsense_web/components/pricing/pricing-section.tsx b/surfsense_web/components/pricing/pricing-section.tsx index b2eef2778..46ceee694 100644 --- a/surfsense_web/components/pricing/pricing-section.tsx +++ b/surfsense_web/components/pricing/pricing-section.tsx @@ -4,6 +4,7 @@ import { AnimatePresence, motion } from "motion/react"; import type React from "react"; import { useEffect, useRef, useState } from "react"; import { Pricing } from "@/components/pricing"; +import { FAQJsonLd } from "@/components/seo/json-ld"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -19,6 +20,8 @@ const demoPlans = [ "500 pages included to start", "$5 in premium credits for paid AI models and premium AI features", "Includes access to OpenAI text, audio and image models", + "AI automations and agents: scheduled and event-triggered workflows", + "Desktop app: Quick, General and Screenshot Assist plus local folder sync", "Realtime Collaborative Group Chats with teammates", "Community support on Discord", ], @@ -37,6 +40,7 @@ const demoPlans = [ "Everything in Free", "Buy 1,000-page packs or $1 in premium credits at $1 each", "Use premium AI models like GPT-5.4, Claude Sonnet 4.6, Gemini 2.5 Pro & 100+ more via OpenRouter", + "Connector write-back to Notion, Slack, Linear & Jira", "Priority support on Discord", ], description: "", @@ -52,6 +56,7 @@ const demoPlans = [ billingText: "", features: [ "Everything in Pay As You Go", + "Custom automation and agent workflows", "On-prem or VPC deployment", "Audit logs and compliance", "SSO, OIDC & SAML", @@ -158,6 +163,31 @@ const faqData: FAQSection[] = [ }, ], }, + { + title: "Automations & Agents", + items: [ + { + question: "What can AI automations and agents do?", + answer: + "AI automations let you run agents on your knowledge base without writing code. You can schedule recurring workflows like daily briefs, weekly status reports, and competitor analysis, or trigger an agent the moment a document lands in a folder. Agents can read across your connected tools, generate summaries and reports, and write results back to Notion, Slack, Linear, and Jira.", + }, + { + question: "Do automations and agents cost extra?", + answer: + "No. There is no separate subscription or add-on fee for automations. Agents use the same page credits and premium credits as the rest of SurfSense. Indexing documents consumes page credits, and premium AI model usage during a workflow consumes premium credits at provider cost. If a workflow only uses free models, it does not touch your premium credits.", + }, + { + question: "How do event-triggered automations work?", + answer: + "Event-triggered automations fire when something happens in your knowledge base, most commonly when a new document enters a folder you are watching. For example, when a PDF lands in your Research folder you can auto-generate a cited summary, or when an invoice is uploaded you can extract the vendor, total, and due date. The agent runs automatically and can post the result to your connected tools.", + }, + { + question: "Can I build an automation without code?", + answer: + "Yes. You can describe the workflow automation you want in plain English in chat, and SurfSense builds the automation for you. For example, ask it to email you a summary of new Notion pages each morning, or post a weekly research digest to Slack, and it sets up the scheduled or event-triggered agent without any code.", + }, + ], + }, { title: "Self-Hosting", items: [ @@ -250,6 +280,7 @@ function PricingFAQ() { return (
+ section.items)} />

Frequently Asked Questions @@ -341,7 +372,7 @@ function PricingBasic() { diff --git a/surfsense_web/components/seo/json-ld.tsx b/surfsense_web/components/seo/json-ld.tsx index 03f3293c3..abfb49f0a 100644 --- a/surfsense_web/components/seo/json-ld.tsx +++ b/surfsense_web/components/seo/json-ld.tsx @@ -77,6 +77,9 @@ export function SoftwareApplicationJsonLd() { "Free access to ChatGPT, Claude AI, and any AI model", "AI-powered semantic search across all connected tools", "Federated search across Slack, Google Drive, Notion, Confluence, GitHub", + "AI automations and agents (scheduled and event-triggered workflows)", + "Connector write-back to Notion, Slack, Linear, Jira", + "Native desktop app with Quick, General, and Screenshot Assist", "No data limits with open source self-hosting", "Real-time collaborative team chats", "Document Q&A with citations", diff --git a/surfsense_web/lib/chat/example-prompts.ts b/surfsense_web/lib/chat/example-prompts.ts new file mode 100644 index 000000000..76b64b2ba --- /dev/null +++ b/surfsense_web/lib/chat/example-prompts.ts @@ -0,0 +1,66 @@ +/** + * Curated example chat prompts shown on the empty new-chat screen. + * + * These mirror the homepage hero's "use case" concept but with runnable chat + * queries, grouped into a few broad categories. Bracketed slots like `[topic]` + * are intentional: clicking a prompt prefills the composer so the user can fill + * them in before sending. + * + * This is a module-scope constant so it is created once, not per render. + */ + +export interface ChatExampleCategory { + /** Stable id used as the Tabs value */ + id: string; + /** Short, human-readable tab label */ + label: string; + /** Runnable example queries for this category */ + prompts: string[]; +} + +export const CHAT_EXAMPLE_CATEGORIES: ChatExampleCategory[] = [ + { + id: "search", + label: "Search & Summarize", + prompts: [ + "Summarize the key points across all the documents in this space.", + "What do my files say about [topic]? Answer with citations.", + "Find every mention of [keyword] and list the sources.", + "Give me a cited briefing on the documents I added this week.", + "Compare these two documents and highlight the differences.", + ], + }, + { + id: "create", + label: "Create", + prompts: [ + "Write a cited research report on [topic] from my documents.", + "Turn this folder into a two-host podcast I can listen to.", + "Create a slide deck and a narrated video overview from these sources.", + "Generate an image to illustrate [concept] for my report.", + "Tailor my resume to this job description so it gets past ATS and lands an interview.", + ], + }, + { + id: "automate", + label: "Automate", + prompts: [ + "Email me a daily brief of new documents in my knowledge base every morning.", + "When a PDF lands in my Research folder, generate a cited AI summary.", + "Generate a weekly status report from my Slack and Gmail every Friday.", + "Build an automation that turns new meeting notes into minutes with action items.", + "Run a monthly competitor analysis report and save it to my workspace.", + ], + }, + { + id: "tools", + label: "Across your tools", + prompts: [ + "Search across my Notion, Slack, Google Drive and Gmail for [topic].", + "Post this research summary to my Notion workspace.", + "Send these meeting action items to our team Slack channel.", + "Create a Jira ticket from this bug report.", + "Open a Linear issue from this feature request.", + ], + }, +]; From cfa52929cc7cf0e3d0874554548a24c047d9a7dd Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 19:29:36 -0700 Subject: [PATCH 133/133] fix(docs): add missing line breaks in README files - Added line breaks in the automations section across multiple language README files for improved readability and formatting consistency. --- README.es.md | 1 + README.hi.md | 1 + README.md | 1 + README.pt-BR.md | 1 + README.zh-CN.md | 1 + 5 files changed, 5 insertions(+) diff --git a/README.es.md b/README.es.md index ffd4f5e5b..ea7623617 100644 --- a/README.es.md +++ b/README.es.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "Guarda este informe generado en Google Drive como un documento." - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. + **Automatizaciones** - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. diff --git a/README.hi.md b/README.hi.md index b12842f9c..10b246385 100644 --- a/README.hi.md +++ b/README.hi.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "इस जनरेट की गई रिपोर्ट को Google Drive में एक डॉक के रूप में सेव करें।" - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। + **ऑटोमेशन** - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। diff --git a/README.md b/README.md index d9beb5da4..a75122892 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "Save this generated report to Google Drive as a doc." - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. + **Automations** - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. diff --git a/README.pt-BR.md b/README.pt-BR.md index 1b8c6f332..db77e5132 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "Salve este relatório gerado no Google Drive como um documento." - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. + **Automações** - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. diff --git a/README.zh-CN.md b/README.zh-CN.md index 131393c77..d3d5330f6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - “把这份生成的报告作为文档保存到 Google Drive。” - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。 + **自动化** - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。