From 2ca6279d403988de401cb6490ba75acda6d85d76 Mon Sep 17 00:00:00 2001 From: Artyom Maisiuk Date: Fri, 18 Apr 2025 13:46:08 +0300 Subject: [PATCH 01/34] add: web_call for OpenAI provider --- lamoom/ai_models/openai/openai_models.py | 457 ++++++++++++++++++++--- lamoom/ai_models/tools.py | 78 ++++ tests/prompts/test_web_call.py | 40 ++ 3 files changed, 517 insertions(+), 58 deletions(-) create mode 100644 lamoom/ai_models/tools.py create mode 100644 tests/prompts/test_web_call.py diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 6dce49c..02c1813 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -11,6 +11,8 @@ from lamoom.ai_models.openai.responses import OpenAIResponse from lamoom.ai_models.utils import get_common_args from lamoom.exceptions import ConnectionLostError +from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS +import json from openai.types.chat import ChatCompletionMessage as Message from lamoom.responses import Prompt @@ -122,12 +124,47 @@ def get_client(self, client_secrets: dict = {}): api_key=client_secrets["api_key"], base_url=self.get_base_url() if self.base_url is None else self.base_url ) + + def _format_tools_for_openai(self, tools: t.List[ToolDefinition]) -> t.List[t.Dict[str, t.Any]]: + openai_tools = [] + for tool_def in tools: + properties = {} + required_params = [] + for param in tool_def.parameters: + # Basic type mapping, expand as needed + param_type = "string" # Default or map more types + if param.type == "number": + param_type = "number" + elif param.type == "boolean": + param_type = "boolean" + + properties[param.name] = { + "type": param_type, + "description": param.description, + } + if param.required: + required_params.append(param.name) + + openai_tools.append({ + "type": "function", + "function": { + "name": tool_def.name, + "description": tool_def.description, + "parameters": { + "type": "object", + "properties": properties, + "required": required_params, + }, + }, + }) + return openai_tools def call_chat_completion( self, messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], functions: t.List[t.Dict[str, str]] = [], + available_tools: t.List[ToolDefinition] = AVAILABLE_TOOLS, stream_function: t.Callable = None, check_connection: t.Callable = None, stream_params: dict = {}, @@ -135,60 +172,364 @@ def call_chat_completion( **kwargs, ) -> OpenAIResponse: - kwargs = { - **{ - "messages": messages, - }, - **self.get_params(), - **kwargs, - } - if functions: - kwargs["tools"] = functions - try: - client = self.get_client(client_secrets) - result = client.chat.completions.create( + client = self.get_client(client_secrets) + + # Keep track of the conversation history including tool interactions + current_messages = list(messages) + current_messages.insert(0, {"role": "system", "content": "Please use a web_call function if you don't have the answer in your knowledge base."}) + + while True: + # if functions: + # kwargs["tools"] = functions + call_kwargs = { + **{ + "messages": current_messages, + }, + **self.get_params(), **kwargs, - ) + } + + # Format and add tools if available AND if the model supports them + openai_formatted_tools = [] + if available_tools: + openai_formatted_tools = self._format_tools_for_openai(available_tools) + + logger.info(f"TOOLS: {openai_formatted_tools}") + + if openai_formatted_tools: + call_kwargs["tools"] = openai_formatted_tools + + try: + result = client.chat.completions.create( + **call_kwargs, + ) - if kwargs.get("stream"): - return OpenAIStreamResponse( - stream_function=stream_function, - check_connection=check_connection, - stream_params=stream_params, - original_result=result, - prompt=Prompt( - messages=kwargs.get("messages"), - functions=kwargs.get("tools"), - max_tokens=max_tokens, - temperature=kwargs.get("temperature"), - top_p=kwargs.get("top_p"), - ), - ).stream() - logger.debug(f"Result: {result.choices[0]}") - return OpenAIResponse( - finish_reason=result.choices[0].finish_reason, - message=result.choices[0].message, - content=result.choices[0].message.content, - original_result=result, - prompt=Prompt( - messages=kwargs.get("messages"), - functions=kwargs.get("tools"), - max_tokens=max_tokens, - temperature=kwargs.get("temperature"), - top_p=kwargs.get("top_p"), - ), - ) - except Exception as e: - logger.exception("[OPENAI] failed to handle chat stream", exc_info=e) - raise_openai_exception(e) + #TODO: handle streaming part later + if kwargs.get("stream"): + return OpenAIStreamResponse( + stream_function=stream_function, + check_connection=check_connection, + stream_params=stream_params, + available_tools=available_tools, + original_result=result, + initial_call_kwargs=call_kwargs, + client=client, + prompt=Prompt( + messages=kwargs.get("messages"), + functions=kwargs.get("tools"), + max_tokens=max_tokens, + temperature=kwargs.get("temperature"), + top_p=kwargs.get("top_p"), + ), + ).stream() + + logger.debug(f"Result: {result.choices[0]}") + choice = result.choices[0] + response_message = choice.message + # *** TOOL CALL CHECK *** + + if response_message.tool_calls: + logger.info(f"Tool call requested: {response_message.tool_calls}") + # Append the assistant's message asking for the tool call + current_messages.append(response_message.to_dict()) # Or convert appropriately + + # Execute tools + for tool_call in response_message.tool_calls: + function_name = tool_call.function.name + try: + function_args = json.loads(tool_call.function.arguments) + for tool in available_tools: + if tool.name == function_name: + tool_definition = tool + break + + if tool_definition and tool_definition.execution_function: + logger.info(f"Executing tool: {function_name} with args: {function_args}") + # *** EXECUTE THE ACTUAL TOOL FUNCTION *** + tool_result = tool_definition.execution_function(**function_args) + logger.info(f"Tool result: {tool_result[:200]}...") # Log truncated result + else: + logger.warning(f"Tool '{function_name}' not found or not executable.") + tool_result = json.dumps({"status": "error", "message": f"Tool '{function_name}' not implemented."}) + + # Append the tool result message + current_messages.append( + { + "tool_call_id": tool_call.id, + "role": "tool", + "content": tool_result, + } + ) + except json.JSONDecodeError: + logger.error(f"Failed to decode arguments for tool {function_name}: {tool_call.function.arguments}") + # Append an error message back + current_messages.append({ + "tool_call_id": tool_call.id, "role": "tool", "name": function_name, + "content": json.dumps({"status": "error", "message": "Invalid arguments format."}) + }) + except Exception as tool_exc: + logger.exception(f"Error executing tool {function_name}", exc_info=tool_exc) + current_messages.append({ + "tool_call_id": tool_call.id, "role": "tool", "name": function_name, + "content": json.dumps({"status": "error", "message": f"Execution failed: {tool_exc}"}) + }) + + # *Loop back* to call the model again with the tool results included + # (Remove tools for the next call if you only want one round of tool use per turn, + # or keep them if you want multi-step tool chains) + # call_kwargs.pop("tools", None) + # call_kwargs.pop("tool_choice", None) + continue # Go to the start of the while loop + + # *** NO TOOL CALL - Normal Response *** + else: + logger.debug(f"Final response message: {response_message}") + return OpenAIResponse( + finish_reason=choice.finish_reason, + message=response_message, + content=response_message.content, + original_result=result, + prompt=Prompt( + messages=messages, # Original messages + functions=call_kwargs.get("tools"), # The tools that were available + max_tokens=max_tokens, + temperature=kwargs.get("temperature"), + top_p=kwargs.get("top_p"), + ), + ) + except Exception as e: + logger.exception("[OPENAI] failed to handle chat stream", exc_info=e) + raise_openai_exception(e) @dataclass(kw_only=True) class OpenAIStreamResponse(OpenAIResponse): + # Existing fields stream_function: t.Callable check_connection: t.Callable stream_params: dict + # Add context needed for tool calls within streaming (as per previous plan) + client: OpenAI # The OpenAI client instance + initial_call_kwargs: dict # Kwargs used for the first call (mutable copy needed) + available_tools: t.List[ToolDefinition] + + # Override stream method with tool handling logic + def stream(self): + """ + Processes the stream, handles potential tool calls by executing tools + and restarting the stream, yields final content via stream_function. + Returns the final populated OpenAIStreamResponse object. + """ + tool_registry = {} # Dictionary to hold tool definitions for easy access + # Populate the tool registry with available tools + for tool in self.available_tools: + tool_registry[tool.name] = tool + + final_content_accumulator = "" # Accumulates text content across stream restarts + current_messages_history = list(self.initial_call_kwargs["messages"]) # Use mutable copy of history + + stream_iterator = self.original_result # Start with the initial stream + + outer_loop_iteration = 0 # To prevent potential infinite loops + max_tool_iterations = 5 # Safety break for sequential tool calls + + while outer_loop_iteration < max_tool_iterations: + outer_loop_iteration += 1 + logger.debug(f"--- Starting stream processing loop iteration: {outer_loop_iteration} ---") + + current_chunk_content = "" # Content accumulated in this specific stream part + tool_calls_accumulator = [] # Buffer for assembling tool call info for this stream part + assistant_message_for_history = None # To store the assistant message asking for tools + current_finish_reason = None # Finish reason for the current stream part + + try: + logger.debug("Processing stream chunks...") + for stream_index, chunk in enumerate(stream_iterator): + if not chunk.choices: + logger.debug("Skipping chunk with no choices.") + continue + + delta = chunk.choices[0].delta + finish_reason = chunk.choices[0].finish_reason + current_finish_reason = finish_reason # Track the latest finish reason + + # --- 1. Accumulate Text Content & Stream Out --- + if delta and delta.content: + text_chunk = delta.content + current_chunk_content += text_chunk + final_content_accumulator += text_chunk + # Use your existing process_message for streaming out and connection checks + self.process_message(text_chunk, stream_index) + + # --- 2. Accumulate Tool Call Information --- + if delta and delta.tool_calls: + # This part handles assembling potentially chunked tool call data + for tool_call_chunk in delta.tool_calls: + index = tool_call_chunk.index + # Ensure list is long enough + while len(tool_calls_accumulator) <= index: + tool_calls_accumulator.append( + {"id": None, "type": "function", "function": {"name": "", "arguments": ""}} + ) + + # Update existing entry + if tool_call_chunk.id: + tool_calls_accumulator[index]["id"] = tool_call_chunk.id + if tool_call_chunk.function: + if tool_call_chunk.function.name: + tool_calls_accumulator[index]["function"]["name"] += tool_call_chunk.function.name + if tool_call_chunk.function.arguments: + tool_calls_accumulator[index]["function"]["arguments"] += tool_call_chunk.function.arguments + + # --- Capture Assistant Role for History --- + # Store the basic structure if we haven't yet and it's an assistant delta + # This helps build the message to add to history if tool calls occur + if delta and delta.role == "assistant" and not assistant_message_for_history: + assistant_message_for_history = {"role": "assistant", "content": None} # Start with no content + + # --- Check if stream part finished --- + if finish_reason: + logger.debug(f"Stream part finished with reason: {finish_reason}") + break # Exit inner loop (chunk processing) + + # --- End of inner chunk processing loop for this stream part --- + + # --- 3. Handle Finish Reason: Tool Calls --- + if current_finish_reason == "tool_calls": + logger.info(f"Tool call(s) requested by model. Assembled calls: {json.dumps(tool_calls_accumulator)}") + + if not assistant_message_for_history: + # Should have been set by a delta, but add fallback + assistant_message_for_history = {"role": "assistant", "content": None} + + # Add the assembled tool calls to the assistant message + assistant_message_for_history["tool_calls"] = tool_calls_accumulator + + # Add the assistant's request to the conversation history + current_messages_history.append(assistant_message_for_history) + + # --- 4. Execute Tools --- + tool_results_messages = [] + for tool_call_data in tool_calls_accumulator: + tool_call_id = tool_call_data.get("id") + function_info = tool_call_data.get("function", {}) + function_name = function_info.get("name") + function_args_str = function_info.get("arguments", "{}") + tool_result_content = "" + + if not tool_call_id or not function_name: + logger.error(f"Incomplete tool call data received: {tool_call_data}") + tool_result_content = json.dumps({"status": "error", "message": "Incomplete tool call data from model."}) + else: + tool_definition = tool_registry.get(function_name) + if tool_definition and tool_definition.execution_function: + try: + logger.info(f"Attempting to parse args for {function_name}: {function_args_str}") + function_args = json.loads(function_args_str) + logger.info(f"Executing tool: '{function_name}' with args: {function_args}") + + # *** EXECUTE THE ACTUAL TOOL FUNCTION *** + tool_result = tool_definition.execution_function(**function_args) + # Ensure result is a string (as required by API) + if not isinstance(tool_result, str): + tool_result_content = json.dumps(tool_result) + else: + tool_result_content = tool_result + logger.info(f"Tool '{function_name}' executed successfully. Result snippet: {tool_result_content[:200]}...") + + except json.JSONDecodeError: + logger.error(f"Failed to decode JSON arguments for tool '{function_name}': {function_args_str}") + tool_result_content = json.dumps({"status": "error", "message": f"Invalid JSON arguments provided for {function_name}."}) + except Exception as tool_exc: + logger.exception(f"Error executing tool '{function_name}'", exc_info=tool_exc) + tool_result_content = json.dumps({"status": "error", "message": f"Execution of tool '{function_name}' failed: {str(tool_exc)}"}) + else: + logger.warning(f"Tool '{function_name}' requested by model but not found in registry or not executable.") + tool_result_content = json.dumps({"status": "error", "message": f"Tool '{function_name}' is not available or implemented."}) + + # Append the tool result message for the next API call + tool_results_messages.append({ + "tool_call_id": tool_call_id, + "role": "tool", + "name": function_name, + "content": tool_result_content, # Result MUST be a string + }) + + # Add all tool results to the history + current_messages_history.extend(tool_results_messages) + + # --- 5. Make a *new* streaming call --- + logger.info("Restarting stream after processing tool calls.") + new_call_kwargs = { + **self.initial_call_kwargs, # Carry over original params + "messages": current_messages_history, # Use updated message history + "stream": True, # MUST be true + # Keep tools available for potential multi-turn calls + "tools": self.initial_call_kwargs.get("tools"), + } + + # Update the stream iterator for the outer loop + stream_iterator = self.client.chat.completions.create(**new_call_kwargs) + + # Continue to the next iteration of the outer while loop + # to process the *new* stream + continue # Goes back to `while outer_loop_iteration < max_tool_iterations:` + + # --- 6. Handle Finish Reason: Stop or Length (Normal Termination) --- + elif current_finish_reason == "stop" or current_finish_reason == "length": + logger.info(f"Final stream finished with reason: {current_finish_reason}") + # The stream has ended successfully (possibly after tool calls) + self.finish_reason = current_finish_reason + # Construct the final message object based on accumulated content + self.message = Message( + content=final_content_accumulator, # All content from all parts + role="assistant" + ) + self.content = final_content_accumulator + # We've reached a terminal state, break the outer loop + break + + # --- 7. Handle Other/Unexpected Finish Reasons or End of Stream --- + else: + # This might happen if the stream ends unexpectedly or with an unknown reason + logger.warning(f"Stream ended with unexpected or no finish_reason: {current_finish_reason}. Last chunk content: {current_chunk_content}") + self.finish_reason = current_finish_reason or "unknown" + self.message = Message(content=final_content_accumulator, role="assistant") + self.content = final_content_accumulator + break # Exit outer loop + + # --- Exception Handling for the inner loop --- + except ConnectionLostError as cle: + logger.error("Connection lost during stream processing.", exc_info=cle) + self.finish_reason = "error_connection_lost" + self.message = Message(content=final_content_accumulator, role="assistant") + self.content = final_content_accumulator + raise cle # Re-raise specific error + except Exception as e: + logger.exception("Exception during stream chunk processing", exc_info=e) + self.finish_reason = "error_processing_stream" + self.message = Message(content=final_content_accumulator, role="assistant") + self.content = final_content_accumulator + # Use your specific exception raising mechanism + raise_openai_exception(e) # This might terminate the process + + # --- End of outer while loop --- + + if outer_loop_iteration >= max_tool_iterations: + logger.warning(f"Reached maximum tool call iterations ({max_tool_iterations}). Stopping.") + self.finish_reason = "error_max_tool_iterations" + # Final message might be incomplete if loop was broken + if not self.message: + self.message = Message(content=final_content_accumulator, role="assistant") + self.content = final_content_accumulator + + # Return self instance with populated data (content, message, finish_reason) + logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") + with open('kwargs_stream.txt', 'w', encoding="utf-8") as f: + f.write(str(new_call_kwargs)) + return self + def process_message(self, text: str, idx: int): if idx % 5 == 0: if not self.check_connection(**self.stream_params): @@ -197,17 +538,17 @@ def process_message(self, text: str, idx: int): return self.stream_function(text, **self.stream_params) - def stream(self): - content = "" - for i, data in enumerate(self.original_result): - if not data.choices: - continue - choice = data.choices[0] - if choice.delta: - content += choice.delta.content or "" - self.process_message(choice.delta.content, i) - self.message = Message( - content=content, - role="assistant", - ) - return self + # def stream(self): + # content = "" + # for i, data in enumerate(self.original_result): + # if not data.choices: + # continue + # choice = data.choices[0] + # if choice.delta: + # content += choice.delta.content or "" + # self.process_message(choice.delta.content, i) + # self.message = Message( + # content=content, + # role="assistant", + # ) + # return self diff --git a/lamoom/ai_models/tools.py b/lamoom/ai_models/tools.py new file mode 100644 index 0000000..8d720d8 --- /dev/null +++ b/lamoom/ai_models/tools.py @@ -0,0 +1,78 @@ +from dataclasses import dataclass, field +import typing as t +import os +from bs4 import BeautifulSoup +import requests +from dotenv import load_dotenv + +load_dotenv() + +@dataclass +class ToolParameter: + name: str + type: str + description: str + required: bool = True + +@dataclass +class ToolDefinition: + name: str + description: str + parameters: t.List[ToolParameter] + execution_function: t.Callable + + +API_KEY = os.getenv("GOOGLE_API_KEY") +SEARCH_ID = os.getenv("SEARCH_ENGINE_ID") + +def scrape_webpage(url: str): + """ + Scrapes the content of a webpage and returns the text. + """ + if not url.startswith(("https://", "http://")): + url = "https://" + url + response = requests.get(url) + + if response.status_code == 200: + soup = BeautifulSoup(response.content, "html.parser") + text = soup.get_text() + clean_text = text.splitlines() + clean_text = [element.strip() + for element in clean_text if element.strip()] + clean_text = '\n'.join(clean_text) + return clean_text + + else: + return "Failed to retrieve the website content." + + +def perform_web_search(query: str): + url = "https://www.googleapis.com/customsearch/v1" + params = { + 'q': query, + 'key': API_KEY, + 'cx': SEARCH_ID, + 'num': 3 + } + + response = requests.get(url, params=params) + results = response.json() + + search_result = "" + + if 'items' in results: + for result in results['items']: + search_result += scrape_webpage(result['link']) + '\n' + + return search_result + +web_call_tool = ToolDefinition( + name="web_call", + description="Performs a web search using a search engine to find up-to-date information or details not present in the internal knowledge.", + parameters=[ + ToolParameter(name="query", type="string", description="The search query to use.", required=True) + ], + execution_function=perform_web_search +) + +AVAILABLE_TOOLS = [web_call_tool] \ No newline at end of file diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py new file mode 100644 index 0000000..ac1bfba --- /dev/null +++ b/tests/prompts/test_web_call.py @@ -0,0 +1,40 @@ + +import logging +import time +from pytest import fixture +from lamoom import Lamoom, Prompt +logger = logging.getLogger(__name__) + + +@fixture +def client(): + import dotenv + dotenv.load_dotenv(dotenv.find_dotenv()) + lamoom = Lamoom() + return lamoom + +def stream_function(text, **kwargs): + print(text) + +def stream_check_connection(validate, **kwargs): + return validate + + +def test_web_call(client): + + context = { + 'text': "Summarize the latest reviews for the movie 'Dune: Part Two'." + } + + # initial version of the prompt + prompt_id = f'test-{time.time()}' + client.service.clear_cache() + prompt = Prompt(id=prompt_id) + prompt.add("{text}", role='user') + + result = client.call(prompt.id, context, "openai/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + + with open('test_web_call.txt', 'w', encoding="utf-8") as f: + f.write(result.content) + + assert result.content \ No newline at end of file From 605ed20cfa41cf594570b924c628f567a1a9d37b Mon Sep 17 00:00:00 2001 From: Artyom Maisiuk Date: Fri, 18 Apr 2025 17:09:04 +0300 Subject: [PATCH 02/34] add: Claude web_call (non-stream) --- lamoom/ai_models/claude/claude_model.py | 154 +++++++++++++++++++++-- lamoom/ai_models/openai/openai_models.py | 3 +- tests/prompts/test_web_call.py | 5 +- 3 files changed, 151 insertions(+), 11 deletions(-) diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index 843d644..e09e30d 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -3,11 +3,13 @@ from lamoom.ai_models.constants import C_200K, C_4K from lamoom.responses import AIResponse +from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS from decimal import Decimal from enum import Enum +import json import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, is_dataclass, asdict from lamoom.ai_models.claude.responses import ClaudeAIReponse from lamoom.ai_models.claude.constants import HAIKU, SONNET, OPUS @@ -17,6 +19,7 @@ from lamoom.responses import Prompt from lamoom.exceptions import RetryableCustomError, ConnectionLostError import anthropic +from anthropic.types import ToolParam, ToolUseBlock, ToolResultBlockParam, ContentBlock, MessageParam logger = logging.getLogger(__name__) @@ -25,7 +28,7 @@ class FamilyModel(Enum): haiku = "Claude 3 Haiku" sonnet = "Claude 3 Sonnet" opus = "Claude 3 Opus" - + @dataclass(kw_only=True) class ClaudeAIModel(AIModel): @@ -65,9 +68,92 @@ def uny_all_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict else: result[-1]["content"] += message.get("content") return result + + def _format_tools_for_anthropic(self, tools: t.List[ToolDefinition]) -> t.List[t.Dict[str, t.Any]]: + """Converts generic ToolDefinition list to Anthropic's tool format.""" + anthropic_tools = [] + for tool_def in tools: + properties = {} + required_params = [] + for param in tool_def.parameters: + param_type = "string" # Default + if param.type == "number": param_type = "number" + elif param.type == "boolean": param_type = "boolean" + elif param.type == "integer": param_type = "integer" + # Add other types ('array', 'object') if needed + properties[param.name] = {"type": param_type, "description": param.description} + if param.required: required_params.append(param.name) + + anthropic_tools.append({ + "name": tool_def.name, + "description": tool_def.description, + "input_schema": {"type": "object", "properties": properties, "required": required_params}, + }) + return anthropic_tools + + def _execute_anthropic_tools( + self, + tool_use_blocks: t.List[ToolUseBlock], # Use the specific Anthropic type + tool_registry: t.Dict[str, ToolDefinition] + ) -> t.List[MessageParam]: + """Executes tools based on Anthropic's tool_use blocks and returns tool_result messages.""" + tools_result_message = {"role": "user", "content": []} + tools_content = [] + + for tool_use in tool_use_blocks: + # Ensure it's actually a tool_use block (safety check) + if not isinstance(tool_use, ToolUseBlock): + logger.warning(f"Skipping non-ToolUseBlock item in tool execution: {type(tool_use)}") + continue + + tool_name = tool_use.name + tool_use_id = tool_use.id + tool_input = tool_use.input or {} # Input is already a dict + tool_result_content_str = "" # Result must be a string + + logger.info(f"Attempting tool execution for '{tool_name}' (ID: {tool_use_id}) with input: {tool_input}") + + tool_definition = tool_registry.get(tool_name) + if tool_definition and tool_definition.execution_function: + try: + tool_result_content_str = tool_definition.execution_function(**tool_input) + logger.info(f"Tool '{tool_name}' executed successfully. Result snippet: {tool_result_content_str[:200]}...") + result_block = { + "type": "tool_result", + "tool_use_id": tool_use_id, + "content": tool_result_content_str + } + + except Exception as tool_exc: + logger.exception(f"Error executing tool '{tool_name}'", exc_info=tool_exc) + error_content = f"Execution of tool '{tool_name}' failed: {str(tool_exc)}" + result_block = { + "type": "tool_result", + "tool_use_id": tool_use_id, + "content": error_content + } + else: + logger.warning(f"Tool '{tool_name}' requested by model but not found in registry or not executable.") + error_content = f"Tool '{tool_name}' is not available or implemented." + result_block = { + "type": "tool_result", + "tool_use_id": tool_use_id, + "content": error_content + } + + # Append the result as a user message containing the tool_result block + tools_content.append(result_block) + + tools_result_message['content'] = tools_content + return tools_result_message - def call(self, messages: t.List[dict], max_tokens: int, client_secrets: dict = {}, **kwargs) -> AIResponse: + def call(self, + messages: t.List[dict], + max_tokens: int, + client_secrets: dict = {}, + available_tools: t.List[ToolDefinition] = AVAILABLE_TOOLS, + **kwargs) -> AIResponse: max_tokens = min(max_tokens, self.max_tokens) common_args = get_common_args(max_tokens) @@ -88,6 +174,16 @@ def call(self, messages: t.List[dict], max_tokens: int, client_secrets: dict = { stream_params = kwargs.get("stream_params") content = "" + + tool_registry = {} # Dictionary to hold tool definitions for easy access + # Populate the tool registry with available tools + for tool in available_tools: + tool_registry[tool.name] = tool + + anthropic_formatted_tools = [] + if available_tools: + anthropic_formatted_tools = self._format_tools_for_anthropic(available_tools) + logger.info(f"Formatted tools for Anthropic: {anthropic_formatted_tools}") try: if kwargs.get("stream"): @@ -104,10 +200,54 @@ def call(self, messages: t.List[dict], max_tokens: int, client_secrets: dict = { content += text idx += 1 else: - response = client.messages.create( - model=self.model, max_tokens=max_tokens, messages=messages - ) - content = response.content[0].text + initial_user_messages = messages # Keep track of original request messages + + current_messages_history = initial_user_messages.copy() + iteration_count = 0 + max_iterations = 5 # Safety break for sequential tool calls + final_response_content = "" + final_stop_reason = None + + # Can be while True: + while iteration_count < max_iterations: + + call_kwargs = { + "model": self.model, + "messages": current_messages_history, + "max_tokens": max_tokens, + } + + logger.debug(f"Calling Claude API with messages: {current_messages_history}") + + if anthropic_formatted_tools: + call_kwargs["tools"] = anthropic_formatted_tools + + response = client.messages.create(**call_kwargs) + final_stop_reason = response.stop_reason + logger.debug(f"Non-streaming response received. Stop Reason: {final_stop_reason}, Role: {response.role}") + + if final_stop_reason == "tool_use": + logger.info(f"Tool use requested (non-streaming).") + current_messages_history.append({'role': response.role, 'content': response.content}) + tool_use_blocks: t.List[ToolUseBlock] = [block for block in response.content if isinstance(block, ToolUseBlock)] + + logger.debug(f"Tool blocks: {tool_use_blocks}") + + if not tool_use_blocks: + logger.warning("Stop reason was 'tool_use', but no ToolUseBlocks found in content.") + break + + tools_result_message = self._execute_anthropic_tools(tool_use_blocks, tool_registry) + current_messages_history.append(tools_result_message) + continue + else: + logger.info(f"Model finished without requesting tools (Reason: {final_stop_reason}).") + text_blocks = [block.text for block in response.content if block.type == "text"] + final_response_content = "\n".join(text_blocks).strip() + break # Exit the while loop, interaction is complete + + content = final_response_content + return ClaudeAIReponse( message=Message(content=content, role="assistant"), content=content, diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 02c1813..4cc2571 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -526,8 +526,7 @@ def stream(self): # Return self instance with populated data (content, message, finish_reason) logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") - with open('kwargs_stream.txt', 'w', encoding="utf-8") as f: - f.write(str(new_call_kwargs)) + return self def process_message(self, text: str, idx: int): diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index ac1bfba..66c01b4 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -32,8 +32,9 @@ def test_web_call(client): prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') - result = client.call(prompt.id, context, "openai/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - + # result = client.call(prompt.id, context, "openai/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") + with open('test_web_call.txt', 'w', encoding="utf-8") as f: f.write(result.content) From cd2ef042a12ddb6836ee94d432dba504dad2e4c1 Mon Sep 17 00:00:00 2001 From: Artyom Maisiuk Date: Sun, 20 Apr 2025 22:34:33 +0300 Subject: [PATCH 03/34] add: custom for non-streaming --- lamoom/ai_models/claude/claude_model.py | 191 ++++------- lamoom/ai_models/openai/openai_models.py | 406 ++++------------------- lamoom/ai_models/tools.py | 107 +++++- tests/prompts/test_web_call.py | 6 +- 4 files changed, 235 insertions(+), 475 deletions(-) diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index e09e30d..89df614 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -3,7 +3,8 @@ from lamoom.ai_models.constants import C_200K, C_4K from lamoom.responses import AIResponse -from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS +from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts, parse_tool_call_block, \ + format_tool_result_message from decimal import Decimal from enum import Enum import json @@ -68,92 +69,15 @@ def uny_all_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict else: result[-1]["content"] += message.get("content") return result - - def _format_tools_for_anthropic(self, tools: t.List[ToolDefinition]) -> t.List[t.Dict[str, t.Any]]: - """Converts generic ToolDefinition list to Anthropic's tool format.""" - anthropic_tools = [] - for tool_def in tools: - properties = {} - required_params = [] - for param in tool_def.parameters: - param_type = "string" # Default - if param.type == "number": param_type = "number" - elif param.type == "boolean": param_type = "boolean" - elif param.type == "integer": param_type = "integer" - # Add other types ('array', 'object') if needed - properties[param.name] = {"type": param_type, "description": param.description} - if param.required: required_params.append(param.name) - - anthropic_tools.append({ - "name": tool_def.name, - "description": tool_def.description, - "input_schema": {"type": "object", "properties": properties, "required": required_params}, - }) - return anthropic_tools - - def _execute_anthropic_tools( - self, - tool_use_blocks: t.List[ToolUseBlock], # Use the specific Anthropic type - tool_registry: t.Dict[str, ToolDefinition] - ) -> t.List[MessageParam]: - """Executes tools based on Anthropic's tool_use blocks and returns tool_result messages.""" - tools_result_message = {"role": "user", "content": []} - tools_content = [] - - for tool_use in tool_use_blocks: - # Ensure it's actually a tool_use block (safety check) - if not isinstance(tool_use, ToolUseBlock): - logger.warning(f"Skipping non-ToolUseBlock item in tool execution: {type(tool_use)}") - continue - - tool_name = tool_use.name - tool_use_id = tool_use.id - tool_input = tool_use.input or {} # Input is already a dict - tool_result_content_str = "" # Result must be a string - - logger.info(f"Attempting tool execution for '{tool_name}' (ID: {tool_use_id}) with input: {tool_input}") - - tool_definition = tool_registry.get(tool_name) - if tool_definition and tool_definition.execution_function: - try: - tool_result_content_str = tool_definition.execution_function(**tool_input) - logger.info(f"Tool '{tool_name}' executed successfully. Result snippet: {tool_result_content_str[:200]}...") - result_block = { - "type": "tool_result", - "tool_use_id": tool_use_id, - "content": tool_result_content_str - } - - except Exception as tool_exc: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=tool_exc) - error_content = f"Execution of tool '{tool_name}' failed: {str(tool_exc)}" - result_block = { - "type": "tool_result", - "tool_use_id": tool_use_id, - "content": error_content - } - else: - logger.warning(f"Tool '{tool_name}' requested by model but not found in registry or not executable.") - error_content = f"Tool '{tool_name}' is not available or implemented." - result_block = { - "type": "tool_result", - "tool_use_id": tool_use_id, - "content": error_content - } - - # Append the result as a user message containing the tool_result block - tools_content.append(result_block) - - tools_result_message['content'] = tools_content - return tools_result_message def call(self, - messages: t.List[dict], - max_tokens: int, - client_secrets: dict = {}, - available_tools: t.List[ToolDefinition] = AVAILABLE_TOOLS, - **kwargs) -> AIResponse: + messages: t.List[dict], + max_tokens: int, + client_secrets: dict = {}, + tool_registry: t.Dict[str, ToolDefinition] = AVAILABLE_TOOLS_REGISTRY, + max_tool_iterations: int = 5, # Safety limit for sequential calls + **kwargs) -> AIResponse: max_tokens = min(max_tokens, self.max_tokens) common_args = get_common_args(max_tokens) @@ -163,9 +87,21 @@ def call(self, **kwargs, } messages = self.uny_all_messages_with_same_role(messages) - + + tool_definitions = [] + for tool_definition in tool_registry.values(): + tool_definitions.append(tool_definition) + + # Inject Tool Prompts into initial messages + current_messages_history = inject_tool_prompts(messages, tool_definitions) + + system_prompt = None + if current_messages_history and current_messages_history[0].get('role') == "system": + system_prompt = current_messages_history[0].get('content') + current_messages_history = current_messages_history[1:] + logger.debug( - f"Calling {messages} with max_tokens {max_tokens} and kwargs {kwargs}" + f"Calling {current_messages_history} with max_tokens {max_tokens} and kwargs {kwargs}" ) client = self.get_client(client_secrets) @@ -174,16 +110,6 @@ def call(self, stream_params = kwargs.get("stream_params") content = "" - - tool_registry = {} # Dictionary to hold tool definitions for easy access - # Populate the tool registry with available tools - for tool in available_tools: - tool_registry[tool.name] = tool - - anthropic_formatted_tools = [] - if available_tools: - anthropic_formatted_tools = self._format_tools_for_anthropic(available_tools) - logger.info(f"Formatted tools for Anthropic: {anthropic_formatted_tools}") try: if kwargs.get("stream"): @@ -200,54 +126,61 @@ def call(self, content += text idx += 1 else: - initial_user_messages = messages # Keep track of original request messages - - current_messages_history = initial_user_messages.copy() iteration_count = 0 - max_iterations = 5 # Safety break for sequential tool calls - final_response_content = "" - final_stop_reason = None - - # Can be while True: - while iteration_count < max_iterations: - + while iteration_count < max_tool_iterations: call_kwargs = { "model": self.model, "messages": current_messages_history, "max_tokens": max_tokens, } + + if system_prompt: + call_kwargs['system'] = system_prompt logger.debug(f"Calling Claude API with messages: {current_messages_history}") - - if anthropic_formatted_tools: - call_kwargs["tools"] = anthropic_formatted_tools response = client.messages.create(**call_kwargs) - final_stop_reason = response.stop_reason - logger.debug(f"Non-streaming response received. Stop Reason: {final_stop_reason}, Role: {response.role}") + # *** TOOL CALL CHECK *** + response_text = response.content[0].text if response.content else "" - if final_stop_reason == "tool_use": - logger.info(f"Tool use requested (non-streaming).") - current_messages_history.append({'role': response.role, 'content': response.content}) - tool_use_blocks: t.List[ToolUseBlock] = [block for block in response.content if isinstance(block, ToolUseBlock)] - - logger.debug(f"Tool blocks: {tool_use_blocks}") - - if not tool_use_blocks: - logger.warning("Stop reason was 'tool_use', but no ToolUseBlocks found in content.") - break - - tools_result_message = self._execute_anthropic_tools(tool_use_blocks, tool_registry) - current_messages_history.append(tools_result_message) - continue + # Parse the response for the block + parsed_tool_call = parse_tool_call_block(response_text) + if parsed_tool_call: + tool_name = parsed_tool_call.get("tool_name") + parameters = parsed_tool_call.get("parameters", {}) + + if response: + assistant_message_to_add = {"role": response.role, "content": response.content} + else: + assistant_message_to_add = {"role": "assistant", "content": response_text} + current_messages_history.append(assistant_message_to_add) + + tool_definition = tool_registry.get(tool_name) + + # Execute the tool + if tool_definition and tool_definition.execution_function: + try: + logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + tool_result_str = tool_definition.execution_function(**parameters) + logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") + except Exception as exec_err: + logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) + tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) + else: + logger.warning(f"Tool '{tool_name}' requested but not found in registry or not executable.") + tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) + + # Add the tool result to history + tool_result_message = format_tool_result_message(tool_name, tool_result_str) + current_messages_history.append(tool_result_message) + + continue else: - logger.info(f"Model finished without requesting tools (Reason: {final_stop_reason}).") text_blocks = [block.text for block in response.content if block.type == "text"] final_response_content = "\n".join(text_blocks).strip() - break # Exit the while loop, interaction is complete + content = final_response_content + break - content = final_response_content - return ClaudeAIReponse( message=Message(content=content, role="assistant"), content=content, diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 4cc2571..c2f47f2 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -11,7 +11,8 @@ from lamoom.ai_models.openai.responses import OpenAIResponse from lamoom.ai_models.utils import get_common_args from lamoom.exceptions import ConnectionLostError -from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS +from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts, parse_tool_call_block, \ + format_tool_result_message import json from openai.types.chat import ChatCompletionMessage as Message @@ -124,47 +125,14 @@ def get_client(self, client_secrets: dict = {}): api_key=client_secrets["api_key"], base_url=self.get_base_url() if self.base_url is None else self.base_url ) - - def _format_tools_for_openai(self, tools: t.List[ToolDefinition]) -> t.List[t.Dict[str, t.Any]]: - openai_tools = [] - for tool_def in tools: - properties = {} - required_params = [] - for param in tool_def.parameters: - # Basic type mapping, expand as needed - param_type = "string" # Default or map more types - if param.type == "number": - param_type = "number" - elif param.type == "boolean": - param_type = "boolean" - - properties[param.name] = { - "type": param_type, - "description": param.description, - } - if param.required: - required_params.append(param.name) - - openai_tools.append({ - "type": "function", - "function": { - "name": tool_def.name, - "description": tool_def.description, - "parameters": { - "type": "object", - "properties": properties, - "required": required_params, - }, - }, - }) - return openai_tools def call_chat_completion( self, messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], functions: t.List[t.Dict[str, str]] = [], - available_tools: t.List[ToolDefinition] = AVAILABLE_TOOLS, + tool_registry: t.Dict[str, ToolDefinition] = AVAILABLE_TOOLS_REGISTRY, + max_tool_iterations: int = 5, # Safety limit for sequential calls stream_function: t.Callable = None, check_connection: t.Callable = None, stream_params: dict = {}, @@ -173,32 +141,27 @@ def call_chat_completion( ) -> OpenAIResponse: client = self.get_client(client_secrets) + tool_definitions = [] + for tool_definition in tool_registry.values(): + tool_definitions.append(tool_definition) + + # Inject Tool Prompts into initial messages + current_messages_history = inject_tool_prompts(messages, tool_definitions) - # Keep track of the conversation history including tool interactions - current_messages = list(messages) - current_messages.insert(0, {"role": "system", "content": "Please use a web_call function if you don't have the answer in your knowledge base."}) - - while True: - # if functions: - # kwargs["tools"] = functions + iteration_count = 0 + while iteration_count < max_tool_iterations: + iteration_count += 1 + + logger.info(f"--- Generic Tool Call Iteration: {iteration_count} ---") + call_kwargs = { **{ - "messages": current_messages, + "messages": current_messages_history, }, **self.get_params(), **kwargs, } - # Format and add tools if available AND if the model supports them - openai_formatted_tools = [] - if available_tools: - openai_formatted_tools = self._format_tools_for_openai(available_tools) - - logger.info(f"TOOLS: {openai_formatted_tools}") - - if openai_formatted_tools: - call_kwargs["tools"] = openai_formatted_tools - try: result = client.chat.completions.create( **call_kwargs, @@ -210,10 +173,7 @@ def call_chat_completion( stream_function=stream_function, check_connection=check_connection, stream_params=stream_params, - available_tools=available_tools, original_result=result, - initial_call_kwargs=call_kwargs, - client=client, prompt=Prompt( messages=kwargs.get("messages"), functions=kwargs.get("tools"), @@ -227,63 +187,46 @@ def call_chat_completion( choice = result.choices[0] response_message = choice.message # *** TOOL CALL CHECK *** + response_text = response_message.content + + # Parse the response for the block + parsed_tool_call = parse_tool_call_block(response_text) - if response_message.tool_calls: - logger.info(f"Tool call requested: {response_message.tool_calls}") - # Append the assistant's message asking for the tool call - current_messages.append(response_message.to_dict()) # Or convert appropriately + if parsed_tool_call: + tool_name = parsed_tool_call.get("tool_name") + parameters = parsed_tool_call.get("parameters", {}) + + # Add the assistant's message (containing the tool call) to history + if response_message: + assistant_message_to_add = response_message.__dict__ + else: + assistant_message_to_add = {"role": "assistant", "content": response_text} + current_messages_history.append(assistant_message_to_add) - # Execute tools - for tool_call in response_message.tool_calls: - function_name = tool_call.function.name - try: - function_args = json.loads(tool_call.function.arguments) - for tool in available_tools: - if tool.name == function_name: - tool_definition = tool - break - if tool_definition and tool_definition.execution_function: - logger.info(f"Executing tool: {function_name} with args: {function_args}") - # *** EXECUTE THE ACTUAL TOOL FUNCTION *** - tool_result = tool_definition.execution_function(**function_args) - logger.info(f"Tool result: {tool_result[:200]}...") # Log truncated result - else: - logger.warning(f"Tool '{function_name}' not found or not executable.") - tool_result = json.dumps({"status": "error", "message": f"Tool '{function_name}' not implemented."}) + tool_definition = tool_registry.get(tool_name) - # Append the tool result message - current_messages.append( - { - "tool_call_id": tool_call.id, - "role": "tool", - "content": tool_result, - } - ) - except json.JSONDecodeError: - logger.error(f"Failed to decode arguments for tool {function_name}: {tool_call.function.arguments}") - # Append an error message back - current_messages.append({ - "tool_call_id": tool_call.id, "role": "tool", "name": function_name, - "content": json.dumps({"status": "error", "message": "Invalid arguments format."}) - }) - except Exception as tool_exc: - logger.exception(f"Error executing tool {function_name}", exc_info=tool_exc) - current_messages.append({ - "tool_call_id": tool_call.id, "role": "tool", "name": function_name, - "content": json.dumps({"status": "error", "message": f"Execution failed: {tool_exc}"}) - }) + # Execute the tool + if tool_definition and tool_definition.execution_function: + try: + logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + tool_result_str = tool_definition.execution_function(**parameters) + logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") + except Exception as exec_err: + logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) + tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) + else: + logger.warning(f"Tool '{tool_name}' requested but not found in registry or not executable.") + tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) - # *Loop back* to call the model again with the tool results included - # (Remove tools for the next call if you only want one round of tool use per turn, - # or keep them if you want multi-step tool chains) - # call_kwargs.pop("tools", None) - # call_kwargs.pop("tool_choice", None) - continue # Go to the start of the while loop + tool_result_message = format_tool_result_message(tool_name, tool_result_str) + current_messages_history.append(tool_result_message) + + continue # *** NO TOOL CALL - Normal Response *** else: - logger.debug(f"Final response message: {response_message}") + logger.info("No tool call block found in response. Finishing.") return OpenAIResponse( finish_reason=choice.finish_reason, message=response_message, @@ -291,7 +234,7 @@ def call_chat_completion( original_result=result, prompt=Prompt( messages=messages, # Original messages - functions=call_kwargs.get("tools"), # The tools that were available + functions=call_kwargs.get("tools"), max_tokens=max_tokens, temperature=kwargs.get("temperature"), top_p=kwargs.get("top_p"), @@ -304,231 +247,10 @@ def call_chat_completion( @dataclass(kw_only=True) class OpenAIStreamResponse(OpenAIResponse): - # Existing fields stream_function: t.Callable check_connection: t.Callable stream_params: dict - # Add context needed for tool calls within streaming (as per previous plan) - client: OpenAI # The OpenAI client instance - initial_call_kwargs: dict # Kwargs used for the first call (mutable copy needed) - available_tools: t.List[ToolDefinition] - - # Override stream method with tool handling logic - def stream(self): - """ - Processes the stream, handles potential tool calls by executing tools - and restarting the stream, yields final content via stream_function. - Returns the final populated OpenAIStreamResponse object. - """ - tool_registry = {} # Dictionary to hold tool definitions for easy access - # Populate the tool registry with available tools - for tool in self.available_tools: - tool_registry[tool.name] = tool - - final_content_accumulator = "" # Accumulates text content across stream restarts - current_messages_history = list(self.initial_call_kwargs["messages"]) # Use mutable copy of history - - stream_iterator = self.original_result # Start with the initial stream - - outer_loop_iteration = 0 # To prevent potential infinite loops - max_tool_iterations = 5 # Safety break for sequential tool calls - - while outer_loop_iteration < max_tool_iterations: - outer_loop_iteration += 1 - logger.debug(f"--- Starting stream processing loop iteration: {outer_loop_iteration} ---") - - current_chunk_content = "" # Content accumulated in this specific stream part - tool_calls_accumulator = [] # Buffer for assembling tool call info for this stream part - assistant_message_for_history = None # To store the assistant message asking for tools - current_finish_reason = None # Finish reason for the current stream part - - try: - logger.debug("Processing stream chunks...") - for stream_index, chunk in enumerate(stream_iterator): - if not chunk.choices: - logger.debug("Skipping chunk with no choices.") - continue - - delta = chunk.choices[0].delta - finish_reason = chunk.choices[0].finish_reason - current_finish_reason = finish_reason # Track the latest finish reason - - # --- 1. Accumulate Text Content & Stream Out --- - if delta and delta.content: - text_chunk = delta.content - current_chunk_content += text_chunk - final_content_accumulator += text_chunk - # Use your existing process_message for streaming out and connection checks - self.process_message(text_chunk, stream_index) - - # --- 2. Accumulate Tool Call Information --- - if delta and delta.tool_calls: - # This part handles assembling potentially chunked tool call data - for tool_call_chunk in delta.tool_calls: - index = tool_call_chunk.index - # Ensure list is long enough - while len(tool_calls_accumulator) <= index: - tool_calls_accumulator.append( - {"id": None, "type": "function", "function": {"name": "", "arguments": ""}} - ) - - # Update existing entry - if tool_call_chunk.id: - tool_calls_accumulator[index]["id"] = tool_call_chunk.id - if tool_call_chunk.function: - if tool_call_chunk.function.name: - tool_calls_accumulator[index]["function"]["name"] += tool_call_chunk.function.name - if tool_call_chunk.function.arguments: - tool_calls_accumulator[index]["function"]["arguments"] += tool_call_chunk.function.arguments - - # --- Capture Assistant Role for History --- - # Store the basic structure if we haven't yet and it's an assistant delta - # This helps build the message to add to history if tool calls occur - if delta and delta.role == "assistant" and not assistant_message_for_history: - assistant_message_for_history = {"role": "assistant", "content": None} # Start with no content - - # --- Check if stream part finished --- - if finish_reason: - logger.debug(f"Stream part finished with reason: {finish_reason}") - break # Exit inner loop (chunk processing) - - # --- End of inner chunk processing loop for this stream part --- - - # --- 3. Handle Finish Reason: Tool Calls --- - if current_finish_reason == "tool_calls": - logger.info(f"Tool call(s) requested by model. Assembled calls: {json.dumps(tool_calls_accumulator)}") - - if not assistant_message_for_history: - # Should have been set by a delta, but add fallback - assistant_message_for_history = {"role": "assistant", "content": None} - - # Add the assembled tool calls to the assistant message - assistant_message_for_history["tool_calls"] = tool_calls_accumulator - - # Add the assistant's request to the conversation history - current_messages_history.append(assistant_message_for_history) - - # --- 4. Execute Tools --- - tool_results_messages = [] - for tool_call_data in tool_calls_accumulator: - tool_call_id = tool_call_data.get("id") - function_info = tool_call_data.get("function", {}) - function_name = function_info.get("name") - function_args_str = function_info.get("arguments", "{}") - tool_result_content = "" - - if not tool_call_id or not function_name: - logger.error(f"Incomplete tool call data received: {tool_call_data}") - tool_result_content = json.dumps({"status": "error", "message": "Incomplete tool call data from model."}) - else: - tool_definition = tool_registry.get(function_name) - if tool_definition and tool_definition.execution_function: - try: - logger.info(f"Attempting to parse args for {function_name}: {function_args_str}") - function_args = json.loads(function_args_str) - logger.info(f"Executing tool: '{function_name}' with args: {function_args}") - - # *** EXECUTE THE ACTUAL TOOL FUNCTION *** - tool_result = tool_definition.execution_function(**function_args) - # Ensure result is a string (as required by API) - if not isinstance(tool_result, str): - tool_result_content = json.dumps(tool_result) - else: - tool_result_content = tool_result - logger.info(f"Tool '{function_name}' executed successfully. Result snippet: {tool_result_content[:200]}...") - - except json.JSONDecodeError: - logger.error(f"Failed to decode JSON arguments for tool '{function_name}': {function_args_str}") - tool_result_content = json.dumps({"status": "error", "message": f"Invalid JSON arguments provided for {function_name}."}) - except Exception as tool_exc: - logger.exception(f"Error executing tool '{function_name}'", exc_info=tool_exc) - tool_result_content = json.dumps({"status": "error", "message": f"Execution of tool '{function_name}' failed: {str(tool_exc)}"}) - else: - logger.warning(f"Tool '{function_name}' requested by model but not found in registry or not executable.") - tool_result_content = json.dumps({"status": "error", "message": f"Tool '{function_name}' is not available or implemented."}) - - # Append the tool result message for the next API call - tool_results_messages.append({ - "tool_call_id": tool_call_id, - "role": "tool", - "name": function_name, - "content": tool_result_content, # Result MUST be a string - }) - - # Add all tool results to the history - current_messages_history.extend(tool_results_messages) - - # --- 5. Make a *new* streaming call --- - logger.info("Restarting stream after processing tool calls.") - new_call_kwargs = { - **self.initial_call_kwargs, # Carry over original params - "messages": current_messages_history, # Use updated message history - "stream": True, # MUST be true - # Keep tools available for potential multi-turn calls - "tools": self.initial_call_kwargs.get("tools"), - } - - # Update the stream iterator for the outer loop - stream_iterator = self.client.chat.completions.create(**new_call_kwargs) - - # Continue to the next iteration of the outer while loop - # to process the *new* stream - continue # Goes back to `while outer_loop_iteration < max_tool_iterations:` - - # --- 6. Handle Finish Reason: Stop or Length (Normal Termination) --- - elif current_finish_reason == "stop" or current_finish_reason == "length": - logger.info(f"Final stream finished with reason: {current_finish_reason}") - # The stream has ended successfully (possibly after tool calls) - self.finish_reason = current_finish_reason - # Construct the final message object based on accumulated content - self.message = Message( - content=final_content_accumulator, # All content from all parts - role="assistant" - ) - self.content = final_content_accumulator - # We've reached a terminal state, break the outer loop - break - - # --- 7. Handle Other/Unexpected Finish Reasons or End of Stream --- - else: - # This might happen if the stream ends unexpectedly or with an unknown reason - logger.warning(f"Stream ended with unexpected or no finish_reason: {current_finish_reason}. Last chunk content: {current_chunk_content}") - self.finish_reason = current_finish_reason or "unknown" - self.message = Message(content=final_content_accumulator, role="assistant") - self.content = final_content_accumulator - break # Exit outer loop - - # --- Exception Handling for the inner loop --- - except ConnectionLostError as cle: - logger.error("Connection lost during stream processing.", exc_info=cle) - self.finish_reason = "error_connection_lost" - self.message = Message(content=final_content_accumulator, role="assistant") - self.content = final_content_accumulator - raise cle # Re-raise specific error - except Exception as e: - logger.exception("Exception during stream chunk processing", exc_info=e) - self.finish_reason = "error_processing_stream" - self.message = Message(content=final_content_accumulator, role="assistant") - self.content = final_content_accumulator - # Use your specific exception raising mechanism - raise_openai_exception(e) # This might terminate the process - - # --- End of outer while loop --- - - if outer_loop_iteration >= max_tool_iterations: - logger.warning(f"Reached maximum tool call iterations ({max_tool_iterations}). Stopping.") - self.finish_reason = "error_max_tool_iterations" - # Final message might be incomplete if loop was broken - if not self.message: - self.message = Message(content=final_content_accumulator, role="assistant") - self.content = final_content_accumulator - - # Return self instance with populated data (content, message, finish_reason) - logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") - - return self - def process_message(self, text: str, idx: int): if idx % 5 == 0: if not self.check_connection(**self.stream_params): @@ -537,17 +259,17 @@ def process_message(self, text: str, idx: int): return self.stream_function(text, **self.stream_params) - # def stream(self): - # content = "" - # for i, data in enumerate(self.original_result): - # if not data.choices: - # continue - # choice = data.choices[0] - # if choice.delta: - # content += choice.delta.content or "" - # self.process_message(choice.delta.content, i) - # self.message = Message( - # content=content, - # role="assistant", - # ) - # return self + def stream(self): + content = "" + for i, data in enumerate(self.original_result): + if not data.choices: + continue + choice = data.choices[0] + if choice.delta: + content += choice.delta.content or "" + self.process_message(choice.delta.content, i) + self.message = Message( + content=content, + role="assistant", + ) + return self \ No newline at end of file diff --git a/lamoom/ai_models/tools.py b/lamoom/ai_models/tools.py index 8d720d8..453b262 100644 --- a/lamoom/ai_models/tools.py +++ b/lamoom/ai_models/tools.py @@ -1,11 +1,38 @@ from dataclasses import dataclass, field import typing as t import os +import json +import re +import logging from bs4 import BeautifulSoup import requests from dotenv import load_dotenv load_dotenv() +logger = logging.getLogger(__name__) + +API_KEY = os.getenv("GOOGLE_API_KEY") +SEARCH_ID = os.getenv("SEARCH_ENGINE_ID") + +# --- Constants for Prompting --- +TOOL_CALL_START_TAG = "" +TOOL_CALL_END_TAG = "" +TOOL_PROMPT_HEADER = """ +You have the following skills available:""" + +TOOL_PROMPT_FOOTER = f""" +If you determine you need to use one of your skills to fulfill the user's request, you MUST format your request as follows, replacing the placeholders: +{TOOL_CALL_START_TAG} +{{ + "tool_name": "", + "parameters": {{ + "": , + "": + // ... include all required parameters for the chosen tool + }} +}} +{TOOL_CALL_END_TAG} +Only include the {TOOL_CALL_START_TAG}...{TOOL_CALL_END_TAG} block if you need to use a skill. Do not add any other text before or after the block if you decide to use a skill.""" @dataclass class ToolParameter: @@ -20,10 +47,82 @@ class ToolDefinition: description: str parameters: t.List[ToolParameter] execution_function: t.Callable + +def format_tool_description(tool: ToolDefinition) -> str: + """Formats a single tool's description for the prompt.""" + param_desc = ", ".join([f"{p.name}: {p.type} ({p.description})" for p in tool.parameters]) + return f"- {tool.name}({{{param_desc}}}) - {tool.description}" -API_KEY = os.getenv("GOOGLE_API_KEY") -SEARCH_ID = os.getenv("SEARCH_ENGINE_ID") +def inject_tool_prompts( + messages: t.List[dict], + available_tools: t.List[ToolDefinition] + ) -> t.List[dict]: + """Injects tool descriptions and usage instructions into the system prompt.""" + if not available_tools: + return messages + + tool_descriptions = "\n".join([format_tool_description(tool) for tool in available_tools]) + tool_system_prompt = f"{TOOL_PROMPT_HEADER}\n{tool_descriptions}\n{TOOL_PROMPT_FOOTER}" + + # Find system prompt or prepend to user prompt + modified_messages = list(messages) # Create a copy + found_system = False + for i, msg in enumerate(modified_messages): + if msg.get("role") == "system": + # Append to existing system prompt + modified_messages[i]["content"] = f"{msg.get('content', '')}\n\n{tool_system_prompt}" + found_system = True + break + + if not found_system: + # Prepend a new system message + modified_messages.insert(0, {"role": "system", "content": tool_system_prompt}) + + logger.debug(f"Injected tool system prompt:\n{tool_system_prompt}") + return modified_messages + +def parse_tool_call_block(text_response: str) -> t.Optional[t.Dict[str, t.Any]]: + """ + Parses the block from the model's text response using regex. + Returns the parsed JSON content as a dict, or None if not found or invalid. + """ + if not text_response: + return None + + # Regex to find the block, allowing for whitespace variations + # DOTALL allows '.' to match newlines within the JSON block + match = re.search( + rf"{re.escape(TOOL_CALL_START_TAG)}(.*?){re.escape(TOOL_CALL_END_TAG)}", + text_response, + re.DOTALL | re.IGNORECASE + ) + + if not match: + return None + + json_content = match.group(1).strip() + logger.debug(f"Found potential tool call JSON block: {json_content}") + + try: + parsed_data = json.loads(json_content) + # Basic validation + if "tool_name" in parsed_data and "parameters" in parsed_data and isinstance(parsed_data["parameters"], dict): + logger.info(f"Successfully parsed tool call: {parsed_data['tool_name']}") + return parsed_data + else: + logger.warning(f"Parsed JSON block lacks required 'tool_name' or 'parameters': {json_content}") + return None + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON from tool call block: {json_content}", exc_info=e) + return None + +def format_tool_result_message(tool_name: str, tool_result: str) -> dict: + """Formats the tool execution result into a message for the history.""" + return { + "role": "user", + "content": f"Tool execution result for '{tool_name}':\n{tool_result}" + } def scrape_webpage(url: str): """ @@ -75,4 +174,6 @@ def perform_web_search(query: str): execution_function=perform_web_search ) -AVAILABLE_TOOLS = [web_call_tool] \ No newline at end of file +AVAILABLE_TOOLS_REGISTRY: t.Dict[str, ToolDefinition] = { + "web_call": web_call_tool, +} \ No newline at end of file diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 66c01b4..2434988 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -33,8 +33,12 @@ def test_web_call(client): prompt.add("{text}", role='user') # result = client.call(prompt.id, context, "openai/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") + # result = client.call(prompt.id, context, "openai/gpt-4o") + # result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") + + result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1") + with open('test_web_call.txt', 'w', encoding="utf-8") as f: f.write(result.content) From 127a907bad2c48469faca426d1cac99ac2597979 Mon Sep 17 00:00:00 2001 From: Artyom Maisiuk Date: Mon, 21 Apr 2025 13:06:26 +0300 Subject: [PATCH 04/34] add: custom for streaming --- lamoom/ai_models/claude/claude_model.py | 113 +- lamoom/ai_models/openai/openai_models.py | 153 ++- poetry.lock | 1395 ++++++++++------------ pyproject.toml | 3 +- tests/prompts/test_web_call.py | 11 +- 5 files changed, 889 insertions(+), 786 deletions(-) diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index 89df614..7d8b523 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -5,12 +5,12 @@ from lamoom.responses import AIResponse from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts, parse_tool_call_block, \ format_tool_result_message -from decimal import Decimal + from enum import Enum import json import typing as t -from dataclasses import dataclass, is_dataclass, asdict +from dataclasses import dataclass from lamoom.ai_models.claude.responses import ClaudeAIReponse from lamoom.ai_models.claude.constants import HAIKU, SONNET, OPUS @@ -88,9 +88,7 @@ def call(self, } messages = self.uny_all_messages_with_same_role(messages) - tool_definitions = [] - for tool_definition in tool_registry.values(): - tool_definitions.append(tool_definition) + tool_definitions = list(tool_registry.values()) # Inject Tool Prompts into initial messages current_messages_history = inject_tool_prompts(messages, tool_definitions) @@ -113,18 +111,99 @@ def call(self, try: if kwargs.get("stream"): - with client.messages.stream( - model=self.model, max_tokens=max_tokens, messages=messages - ) as stream: - idx = 0 - for text in stream.text_stream: - if idx % 5 == 0: - if not check_connection(**stream_params): - raise ConnectionLostError("Connection was lost!") - - stream_function(text, **stream_params) - content += text - idx += 1 + iteration_count = 0 + while iteration_count < max_tool_iterations: + iteration_count += 1 + logger.info(f"--- Custom Claude Tool Streaming Iteration: {iteration_count} ---") + + current_stream_part_content = "" + stream_stop_reason = None # Reason for this specific stream part + + call_kwargs = { + "model": self.model, + "max_tokens": max_tokens, + "messages": current_messages_history, # Use current history + } + + if system_prompt: + call_kwargs["system"] = system_prompt + + stream_idx = 0 + try: + logger.debug(f"Initiating Claude stream. History length: {len(current_messages_history)}") + with client.messages.stream(**call_kwargs) as stream_handler: + for text_chunk in stream_handler.text_stream: + stream_idx += 1 + # Check connection periodically + if stream_idx % 5 == 0: + if not check_connection(**stream_params): + raise ConnectionLostError("Connection was lost!") + # Accumulate content + current_stream_part_content += text_chunk + if text_chunk: + stream_function(text_chunk, **stream_params) + + # Get final message details after stream ends + final_message_status = stream_handler.get_final_message() + stream_stop_reason = final_message_status.stop_reason + logger.debug(f"Claude stream part finished. Stop Reason: {stream_stop_reason}") + + except ConnectionLostError: # Catch specifically + raise # Re-raise immediately + except anthropic.APIError as e: + logger.exception("[CLAUDEAI] API Error during stream processing", exc_info=e) + raise RetryableCustomError(f"Claude AI API Error: {e}") from e + except Exception as e: + logger.exception("Exception during Claude stream processing", exc_info=e) + raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e + + # --- After processing stream part --- + logger.debug(f"Accumulated stream content for parsing: {current_stream_part_content[:500]}...") + + # Add the raw assistant response to history *before* parsing + assistant_message_to_add = {"role": "assistant", "content": current_stream_part_content} + current_messages_history.append(assistant_message_to_add) + + # Parse the accumulated text for the custom tool block + parsed_tool_call = parse_tool_call_block(current_stream_part_content) + + if parsed_tool_call: + tool_name = parsed_tool_call.get("tool_name") + parameters = parsed_tool_call.get("parameters", {}) + logger.info(f"Custom tool call block parsed: {tool_name}") + + tool_definition = tool_registry.get(tool_name) + tool_result_str = "" # Initialize + + # Execute the tool + if tool_definition and tool_definition.execution_function: + try: + logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + tool_result_str = tool_definition.execution_function(**parameters) + logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") + except Exception as exec_err: + logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) + tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) + else: + logger.warning(f"Tool '{tool_name}' requested but not found/executable.") + tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) + + tool_result_message = format_tool_result_message(tool_name, tool_result_str) + current_messages_history.append(tool_result_message) + + continue + + # --- No Tool Call Found in this stream part --- + else: + logger.info("No custom tool call block found in this stream part. Finishing.") + content = current_stream_part_content # Final content is from this last part + break + + # --- End of streaming while loop --- + if iteration_count >= max_tool_iterations: + logger.warning(f"Reached max tool call iterations ({max_tool_iterations}) during custom Claude streaming.") + # Use content from the last attempt as final content + content = current_stream_part_content else: iteration_count = 0 while iteration_count < max_tool_iterations: diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index c2f47f2..8e71201 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -1,6 +1,6 @@ import logging import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, field from decimal import Decimal from enum import Enum @@ -141,9 +141,7 @@ def call_chat_completion( ) -> OpenAIResponse: client = self.get_client(client_secrets) - tool_definitions = [] - for tool_definition in tool_registry.values(): - tool_definitions.append(tool_definition) + tool_definitions = list(tool_registry.values()) # Inject Tool Prompts into initial messages current_messages_history = inject_tool_prompts(messages, tool_definitions) @@ -169,11 +167,17 @@ def call_chat_completion( #TODO: handle streaming part later if kwargs.get("stream"): + return OpenAIStreamResponse( stream_function=stream_function, check_connection=check_connection, stream_params=stream_params, original_result=result, + client=client, + initial_call_kwargs=call_kwargs, + tool_registry=tool_registry, + initial_messages_history=current_messages_history, + max_tool_iterations=max_tool_iterations, prompt=Prompt( messages=kwargs.get("messages"), functions=kwargs.get("tools"), @@ -203,7 +207,6 @@ def call_chat_completion( assistant_message_to_add = {"role": "assistant", "content": response_text} current_messages_history.append(assistant_message_to_add) - tool_definition = tool_registry.get(tool_name) # Execute the tool @@ -250,6 +253,19 @@ class OpenAIStreamResponse(OpenAIResponse): stream_function: t.Callable check_connection: t.Callable stream_params: dict + + client: OpenAI + tool_registry: t.Dict[str, ToolDefinition] + max_tool_iterations: int + initial_call_kwargs: dict # Original non-message kwargs + initial_messages_history: t.List[dict] # Original messages list *with tool prompts injected* + + # Internal state for the stream method + _current_messages_history: t.List[dict] = field(init=False, default_factory=list) + _total_accumulated_content: str = field(init=False, default="") + + def __post_init__(self): + self._current_messages_history = list(self.initial_messages_history) def process_message(self, text: str, idx: int): if idx % 5 == 0: @@ -260,16 +276,119 @@ def process_message(self, text: str, idx: int): self.stream_function(text, **self.stream_params) def stream(self): - content = "" - for i, data in enumerate(self.original_result): - if not data.choices: - continue - choice = data.choices[0] - if choice.delta: - content += choice.delta.content or "" - self.process_message(choice.delta.content, i) - self.message = Message( - content=content, - role="assistant", - ) + """ + Processes the stream, parses for custom blocks, + executes tools, and restarts the stream if necessary. + """ + iteration_count = 0 + current_stream_iterator = self.original_result # Start with the initial iterator + + while iteration_count < self.max_tool_iterations: + iteration_count += 1 + logger.info(f"--- Custom Tool Streaming Iteration: {iteration_count} ---") + + current_stream_part_content = "" + current_finish_reason = None + assistant_response_for_history = {"role": "assistant", "content": ""} # To store raw assistant text + + stream_idx = 0 + try: + logger.debug("Processing stream chunks...") + for chunk in current_stream_iterator: + stream_idx += 1 + if not chunk.choices: + continue + + delta = chunk.choices[0].delta + finish_reason = chunk.choices[0].finish_reason + current_finish_reason = finish_reason # Track the latest finish reason + + # Accumulate content and stream out using process_message + if delta and delta.content: + text_chunk = delta.content + current_stream_part_content += text_chunk + self.process_message(text_chunk, stream_idx) + + # If stream part finished, break inner loop + if finish_reason: + logger.debug(f"Stream part finished with reason: {finish_reason}") + break + + # --- After processing chunks for this stream part --- + logger.debug(f"Accumulated content for parsing: {current_stream_part_content[:500]}...") + # Update the assistant message content for history + assistant_response_for_history["content"] = current_stream_part_content + # Add the raw assistant response to our internal history *before* checking for tool call + self._current_messages_history.append(assistant_response_for_history) + + # Parse the *accumulated* text for the custom tool block + parsed_tool_call = parse_tool_call_block(current_stream_part_content) + + if parsed_tool_call: + tool_name = parsed_tool_call.get("tool_name") + parameters = parsed_tool_call.get("parameters", {}) + logger.info(f"Custom tool call block parsed: {tool_name}") + + tool_definition = self.tool_registry.get(tool_name) + tool_result_str = "" + + # Execute the tool + if tool_definition and tool_definition.execution_function: + try: + logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + # *** EXECUTE TOOL *** + tool_result_str = tool_definition.execution_function(**parameters) + logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") + except Exception as exec_err: + logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) + tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) + else: + logger.warning(f"Tool '{tool_name}' requested but not found in registry or not executable.") + tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) + + tool_result_message = format_tool_result_message(tool_name, tool_result_str) + self._current_messages_history.append(tool_result_message) + + # --- Make a *new* streaming call --- + logger.info("Restarting stream after custom tool execution.") + new_call_kwargs = { + **self.initial_call_kwargs, + "messages": self._current_messages_history, + "stream": True, + } + + current_stream_iterator = self.client.chat.completions.create(**new_call_kwargs) + continue + + # --- No Tool Call Found in this stream part --- + else: + logger.info("No custom tool call block found in this stream part.") + self._total_accumulated_content = current_stream_part_content # Store final content + self.finish_reason = current_finish_reason or "stop" + break + + # --- Exception Handling for the inner loop --- + except ConnectionLostError as cle: + logger.error("Connection lost during stream processing.", exc_info=cle) + self.finish_reason = "error_connection_lost" + raise cle + except Exception as e: + logger.exception("Exception during custom stream chunk processing", exc_info=e) + self.finish_reason = "error_processing_stream" + raise_openai_exception(e) + + # --- End of outer while loop --- + if iteration_count >= self.max_tool_iterations: + logger.warning(f"Reached maximum tool call iterations ({self.max_tool_iterations}) during custom streaming.") + self.finish_reason = "error_max_tool_iterations" + self._total_accumulated_content = current_stream_part_content + + # Populate final fields of the response object + self.content = self._total_accumulated_content + self.message = Message( # Use your Message class structure + content=self.content, + role="assistant" + ) + + logger.debug(f"Custom stream processing complete. Final finish reason: {self.finish_reason}") return self \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 8bf1da0..343947f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -18,7 +17,6 @@ version = "0.31.2" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "anthropic-0.31.2-py3-none-any.whl", hash = "sha256:28d176b98c72615bfae30f0a9eee6297cc33bf52535d38156fc2805556e2f09b"}, {file = "anthropic-0.31.2.tar.gz", hash = "sha256:0134b73df8d1f142fc68675fbadb75e920054e9e3437b99df63f10f0fc6ac26f"}, @@ -40,14 +38,13 @@ vertex = ["google-auth (>=2,<3)"] [[package]] name = "anyio" -version = "4.8.0" +version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] @@ -57,8 +54,8 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -67,8 +64,6 @@ version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = ">=3.6" -groups = ["dev"] -markers = "platform_system == \"Darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -80,7 +75,6 @@ version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -96,7 +90,6 @@ version = "2.3.2" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, @@ -106,13 +99,34 @@ files = [ pycodestyle = ">=2.12.0" tomli = {version = "*", markers = "python_version < \"3.11\""} +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -159,7 +173,6 @@ version = "1.2.2.post1" description = "A simple, correct Python build frontend" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, @@ -174,7 +187,7 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] uv = ["uv (>=0.1.18)"] virtualenv = ["virtualenv (>=20.0.35)"] @@ -185,7 +198,6 @@ version = "0.14.2" description = "httplib2 caching for requests" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, @@ -207,7 +219,6 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -219,8 +230,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\" or sys_platform == \"darwin\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -300,7 +309,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -312,7 +320,6 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -414,7 +421,6 @@ version = "2.1.0" description = "Cleo allows you to create beautiful and testable command-line interfaces." optional = false python-versions = ">=3.7,<4.0" -groups = ["dev"] files = [ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, @@ -430,7 +436,6 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -445,12 +450,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\""} [[package]] name = "comm" @@ -458,7 +461,6 @@ version = "0.2.2" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, @@ -472,82 +474,81 @@ test = ["pytest"] [[package]] name = "coverage" -version = "7.6.12" +version = "7.8.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, - {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, - {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, - {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, - {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, - {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, - {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, - {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, - {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, - {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, - {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, - {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, - {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, - {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, - {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, - {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, - {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, - {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, - {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, - {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, - {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, - {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, - {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, - {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, - {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, - {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, - {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, - {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, - {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, - {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, - {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, - {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, - {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, - {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, - {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, +files = [ + {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, + {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, + {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, + {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, + {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, + {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, + {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, + {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, + {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, + {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, + {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, + {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, + {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, + {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, + {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, + {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "crashtest" @@ -555,7 +556,6 @@ version = "0.4.1" description = "Manage Python errors with ease" optional = false python-versions = ">=3.7,<4.0" -groups = ["dev"] files = [ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, @@ -567,8 +567,6 @@ version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, @@ -614,38 +612,37 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "debugpy" -version = "1.8.12" +version = "1.8.14" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "debugpy-1.8.12-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a"}, - {file = "debugpy-1.8.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45"}, - {file = "debugpy-1.8.12-cp310-cp310-win32.whl", hash = "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c"}, - {file = "debugpy-1.8.12-cp310-cp310-win_amd64.whl", hash = "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9"}, - {file = "debugpy-1.8.12-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5"}, - {file = "debugpy-1.8.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7"}, - {file = "debugpy-1.8.12-cp311-cp311-win32.whl", hash = "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb"}, - {file = "debugpy-1.8.12-cp311-cp311-win_amd64.whl", hash = "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1"}, - {file = "debugpy-1.8.12-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498"}, - {file = "debugpy-1.8.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06"}, - {file = "debugpy-1.8.12-cp312-cp312-win32.whl", hash = "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d"}, - {file = "debugpy-1.8.12-cp312-cp312-win_amd64.whl", hash = "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969"}, - {file = "debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f"}, - {file = "debugpy-1.8.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9"}, - {file = "debugpy-1.8.12-cp313-cp313-win32.whl", hash = "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180"}, - {file = "debugpy-1.8.12-cp313-cp313-win_amd64.whl", hash = "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c"}, - {file = "debugpy-1.8.12-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:b0232cd42506d0c94f9328aaf0d1d0785f90f87ae72d9759df7e5051be039738"}, - {file = "debugpy-1.8.12-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af40506a59450f1315168d47a970db1a65aaab5df3833ac389d2899a5d63b3f"}, - {file = "debugpy-1.8.12-cp38-cp38-win32.whl", hash = "sha256:5cc45235fefac57f52680902b7d197fb2f3650112379a6fa9aa1b1c1d3ed3f02"}, - {file = "debugpy-1.8.12-cp38-cp38-win_amd64.whl", hash = "sha256:557cc55b51ab2f3371e238804ffc8510b6ef087673303890f57a24195d096e61"}, - {file = "debugpy-1.8.12-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:b5c6c967d02fee30e157ab5227706f965d5c37679c687b1e7bbc5d9e7128bd41"}, - {file = "debugpy-1.8.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a77f422f31f170c4b7e9ca58eae2a6c8e04da54121900651dfa8e66c29901a"}, - {file = "debugpy-1.8.12-cp39-cp39-win32.whl", hash = "sha256:a4042edef80364239f5b7b5764e55fd3ffd40c32cf6753da9bda4ff0ac466018"}, - {file = "debugpy-1.8.12-cp39-cp39-win_amd64.whl", hash = "sha256:f30b03b0f27608a0b26c75f0bb8a880c752c0e0b01090551b9d87c7d783e2069"}, - {file = "debugpy-1.8.12-py2.py3-none-any.whl", hash = "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6"}, - {file = "debugpy-1.8.12.tar.gz", hash = "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce"}, +files = [ + {file = "debugpy-1.8.14-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:93fee753097e85623cab1c0e6a68c76308cd9f13ffdf44127e6fab4fbf024339"}, + {file = "debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d937d93ae4fa51cdc94d3e865f535f185d5f9748efb41d0d49e33bf3365bd79"}, + {file = "debugpy-1.8.14-cp310-cp310-win32.whl", hash = "sha256:c442f20577b38cc7a9aafecffe1094f78f07fb8423c3dddb384e6b8f49fd2987"}, + {file = "debugpy-1.8.14-cp310-cp310-win_amd64.whl", hash = "sha256:f117dedda6d969c5c9483e23f573b38f4e39412845c7bc487b6f2648df30fe84"}, + {file = "debugpy-1.8.14-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:1b2ac8c13b2645e0b1eaf30e816404990fbdb168e193322be8f545e8c01644a9"}, + {file = "debugpy-1.8.14-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf431c343a99384ac7eab2f763980724834f933a271e90496944195318c619e2"}, + {file = "debugpy-1.8.14-cp311-cp311-win32.whl", hash = "sha256:c99295c76161ad8d507b413cd33422d7c542889fbb73035889420ac1fad354f2"}, + {file = "debugpy-1.8.14-cp311-cp311-win_amd64.whl", hash = "sha256:7816acea4a46d7e4e50ad8d09d963a680ecc814ae31cdef3622eb05ccacf7b01"}, + {file = "debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84"}, + {file = "debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826"}, + {file = "debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f"}, + {file = "debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f"}, + {file = "debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f"}, + {file = "debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15"}, + {file = "debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e"}, + {file = "debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e"}, + {file = "debugpy-1.8.14-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:d5582bcbe42917bc6bbe5c12db1bffdf21f6bfc28d4554b738bf08d50dc0c8c3"}, + {file = "debugpy-1.8.14-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5349b7c3735b766a281873fbe32ca9cca343d4cc11ba4a743f84cb854339ff35"}, + {file = "debugpy-1.8.14-cp38-cp38-win32.whl", hash = "sha256:7118d462fe9724c887d355eef395fae68bc764fd862cdca94e70dcb9ade8a23d"}, + {file = "debugpy-1.8.14-cp38-cp38-win_amd64.whl", hash = "sha256:d235e4fa78af2de4e5609073972700523e372cf5601742449970110d565ca28c"}, + {file = "debugpy-1.8.14-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:413512d35ff52c2fb0fd2d65e69f373ffd24f0ecb1fac514c04a668599c5ce7f"}, + {file = "debugpy-1.8.14-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c9156f7524a0d70b7a7e22b2e311d8ba76a15496fb00730e46dcdeedb9e1eea"}, + {file = "debugpy-1.8.14-cp39-cp39-win32.whl", hash = "sha256:b44985f97cc3dd9d52c42eb59ee9d7ee0c4e7ecd62bca704891f997de4cef23d"}, + {file = "debugpy-1.8.14-cp39-cp39-win_amd64.whl", hash = "sha256:b1528cfee6c1b1c698eb10b6b096c598738a8238822d218173d21c3086de8123"}, + {file = "debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20"}, + {file = "debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322"}, ] [[package]] @@ -654,7 +651,6 @@ version = "5.2.1" description = "Decorators for Humans" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, @@ -666,7 +662,6 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -678,7 +673,6 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -690,7 +684,6 @@ version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, @@ -702,7 +695,6 @@ version = "0.21.7" description = "Python Git Library" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, @@ -790,8 +782,6 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -806,14 +796,13 @@ version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] [[package]] name = "fastjsonschema" @@ -821,7 +810,6 @@ version = "2.21.1" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, @@ -832,48 +820,45 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.17.0" +version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, - {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "flake8" -version = "7.1.2" +version = "7.2.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = ">=3.8.1" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a"}, - {file = "flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd"}, + {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, + {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.12.0,<2.13.0" -pyflakes = ">=3.2.0,<3.3.0" +pycodestyle = ">=2.13.0,<2.14.0" +pyflakes = ">=3.3.0,<3.4.0" [[package]] name = "fsspec" -version = "2025.2.0" +version = "2025.3.2" description = "File-system specification" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" files = [ - {file = "fsspec-2025.2.0-py3-none-any.whl", hash = "sha256:9de2ad9ce1f85e1931858535bc882543171d197001a0a5eb2ddc04f1781ab95b"}, - {file = "fsspec-2025.2.0.tar.gz", hash = "sha256:1c24b16eaa0a1798afa0337aa0db9b256718ab2a89c425371f5628d22c3b6afd"}, + {file = "fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711"}, + {file = "fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6"}, ] [package.extras] @@ -910,7 +895,6 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -918,14 +902,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.8" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, + {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, ] [package.dependencies] @@ -944,7 +927,6 @@ version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -958,7 +940,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -966,14 +948,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "huggingface-hub" -version = "0.29.1" +version = "0.30.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" -groups = ["main"] files = [ - {file = "huggingface_hub-0.29.1-py3-none-any.whl", hash = "sha256:352f69caf16566c7b6de84b54a822f6238e17ddd8ae3da4f8f2272aea5b198d5"}, - {file = "huggingface_hub-0.29.1.tar.gz", hash = "sha256:9524eae42077b8ff4fc459ceb7a514eca1c1232b775276b009709fe2a084f250"}, + {file = "huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28"}, + {file = "huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466"}, ] [package.dependencies] @@ -991,6 +972,7 @@ cli = ["InquirerPy (==0.3.4)"] dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] hf-transfer = ["hf-transfer (>=0.1.4)"] +hf-xet = ["hf-xet (>=0.1.4)"] inference = ["aiohttp"] quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] tensorflow = ["graphviz", "pydot", "tensorflow"] @@ -1001,14 +983,13 @@ typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "t [[package]] name = "identify" -version = "2.6.8" +version = "2.6.10" description = "File identification library for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ - {file = "identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255"}, - {file = "identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc"}, + {file = "identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25"}, + {file = "identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8"}, ] [package.extras] @@ -1020,7 +1001,6 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1035,7 +1015,6 @@ version = "8.6.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, @@ -1045,24 +1024,23 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -1071,7 +1049,6 @@ version = "0.7.0" description = "A library for installing Python wheels." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, @@ -1083,7 +1060,6 @@ version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, @@ -1117,7 +1093,6 @@ version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, @@ -1155,7 +1130,6 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -1170,7 +1144,6 @@ version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, @@ -1189,7 +1162,6 @@ version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -1209,101 +1181,98 @@ version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, ] [package.extras] -test = ["async-timeout ; python_version < \"3.11\"", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] trio = ["trio"] [[package]] name = "jiter" -version = "0.8.2" +version = "0.9.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b"}, - {file = "jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08"}, - {file = "jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49"}, - {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d"}, - {file = "jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff"}, - {file = "jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43"}, - {file = "jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105"}, - {file = "jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b"}, - {file = "jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586"}, - {file = "jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc"}, - {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88"}, - {file = "jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6"}, - {file = "jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44"}, - {file = "jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855"}, - {file = "jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f"}, - {file = "jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887"}, - {file = "jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d"}, - {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152"}, - {file = "jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29"}, - {file = "jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e"}, - {file = "jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c"}, - {file = "jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84"}, - {file = "jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef"}, - {file = "jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1"}, - {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9"}, - {file = "jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05"}, - {file = "jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a"}, - {file = "jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865"}, - {file = "jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca"}, - {file = "jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0"}, - {file = "jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566"}, - {file = "jiter-0.8.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9e1fa156ee9454642adb7e7234a383884452532bc9d53d5af2d18d98ada1d79c"}, - {file = "jiter-0.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cf5dfa9956d96ff2efb0f8e9c7d055904012c952539a774305aaaf3abdf3d6c"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e52bf98c7e727dd44f7c4acb980cb988448faeafed8433c867888268899b298b"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a2ecaa3c23e7a7cf86d00eda3390c232f4d533cd9ddea4b04f5d0644faf642c5"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:08d4c92bf480e19fc3f2717c9ce2aa31dceaa9163839a311424b6862252c943e"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99d9a1eded738299ba8e106c6779ce5c3893cffa0e32e4485d680588adae6db8"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d20be8b7f606df096e08b0b1b4a3c6f0515e8dac296881fe7461dfa0fb5ec817"}, - {file = "jiter-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d33f94615fcaf872f7fd8cd98ac3b429e435c77619777e8a449d9d27e01134d1"}, - {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:317b25e98a35ffec5c67efe56a4e9970852632c810d35b34ecdd70cc0e47b3b6"}, - {file = "jiter-0.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fc9043259ee430ecd71d178fccabd8c332a3bf1e81e50cae43cc2b28d19e4cb7"}, - {file = "jiter-0.8.2-cp38-cp38-win32.whl", hash = "sha256:fc5adda618205bd4678b146612ce44c3cbfdee9697951f2c0ffdef1f26d72b63"}, - {file = "jiter-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cd646c827b4f85ef4a78e4e58f4f5854fae0caf3db91b59f0d73731448a970c6"}, - {file = "jiter-0.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e41e75344acef3fc59ba4765df29f107f309ca9e8eace5baacabd9217e52a5ee"}, - {file = "jiter-0.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f22b16b35d5c1df9dfd58843ab2cd25e6bf15191f5a236bed177afade507bfc"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7200b8f7619d36aa51c803fd52020a2dfbea36ffec1b5e22cab11fd34d95a6d"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70bf4c43652cc294040dbb62256c83c8718370c8b93dd93d934b9a7bf6c4f53c"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9d471356dc16f84ed48768b8ee79f29514295c7295cb41e1133ec0b2b8d637d"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:859e8eb3507894093d01929e12e267f83b1d5f6221099d3ec976f0c995cb6bd9"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaa58399c01db555346647a907b4ef6d4f584b123943be6ed5588c3f2359c9f4"}, - {file = "jiter-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f2d5ed877f089862f4c7aacf3a542627c1496f972a34d0474ce85ee7d939c27"}, - {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:03c9df035d4f8d647f8c210ddc2ae0728387275340668fb30d2421e17d9a0841"}, - {file = "jiter-0.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8bd2a824d08d8977bb2794ea2682f898ad3d8837932e3a74937e93d62ecbb637"}, - {file = "jiter-0.8.2-cp39-cp39-win32.whl", hash = "sha256:ca29b6371ebc40e496995c94b988a101b9fbbed48a51190a4461fcb0a68b4a36"}, - {file = "jiter-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c0dfbd1be3cbefc7510102370d86e35d1d53e5a93d48519688b1bf0f761160a"}, - {file = "jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d"}, +files = [ + {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, + {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678"}, + {file = "jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4"}, + {file = "jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965"}, + {file = "jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2"}, + {file = "jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"}, + {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"}, + {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d"}, + {file = "jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53"}, + {file = "jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7"}, + {file = "jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001"}, + {file = "jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a"}, + {file = "jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4a2d16360d0642cd68236f931b85fe50288834c383492e4279d9f1792e309571"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e84ed1c9c9ec10bbb8c37f450077cbe3c0d4e8c2b19f0a49a60ac7ace73c7452"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f3c848209ccd1bfa344a1240763975ca917de753c7875c77ec3034f4151d06c"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7825f46e50646bee937e0f849d14ef3a417910966136f59cd1eb848b8b5bb3e4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d82a811928b26d1a6311a886b2566f68ccf2b23cf3bfed042e18686f1f22c2d7"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c058ecb51763a67f019ae423b1cbe3fa90f7ee6280c31a1baa6ccc0c0e2d06e"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9897115ad716c48f0120c1f0c4efae348ec47037319a6c63b2d7838bb53aaef4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:351f4c90a24c4fb8c87c6a73af2944c440494ed2bea2094feecacb75c50398ae"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d45807b0f236c485e1e525e2ce3a854807dfe28ccf0d013dd4a563395e28008a"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1537a890724ba00fdba21787010ac6f24dad47f763410e9e1093277913592784"}, + {file = "jiter-0.9.0-cp38-cp38-win32.whl", hash = "sha256:e3630ec20cbeaddd4b65513fa3857e1b7c4190d4481ef07fb63d0fad59033321"}, + {file = "jiter-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:2685f44bf80e95f8910553bf2d33b9c87bf25fceae6e9f0c1355f75d2922b0ee"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e"}, + {file = "jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95"}, + {file = "jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa"}, + {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"}, ] [[package]] @@ -1312,7 +1281,6 @@ version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -1328,7 +1296,7 @@ traitlets = ">=5.3" [package.extras] docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] [[package]] name = "jupyter-core" @@ -1336,7 +1304,6 @@ version = "5.7.2" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, @@ -1357,7 +1324,6 @@ version = "24.3.1" description = "Store and access your passwords safely." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, @@ -1373,7 +1339,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-ruff (>=0.2.1)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "markdown-it-py" @@ -1381,7 +1347,6 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1406,7 +1371,6 @@ version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -1421,7 +1385,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1433,7 +1396,6 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1445,7 +1407,6 @@ version = "10.6.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, @@ -1457,7 +1418,6 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, @@ -1531,7 +1491,6 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1543,7 +1502,6 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -1555,7 +1513,6 @@ version = "0.2.21" description = "Python binding to Ammonia HTML sanitizer Rust crate" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286"}, {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde"}, @@ -1589,7 +1546,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1597,14 +1553,13 @@ files = [ [[package]] name = "openai" -version = "1.65.1" +version = "1.75.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ - {file = "openai-1.65.1-py3-none-any.whl", hash = "sha256:396652a6452dd42791b3ad8a3aab09b1feb7c1c4550a672586fb300760a8e204"}, - {file = "openai-1.65.1.tar.gz", hash = "sha256:9d9370a20d2b8c3ce319fd2194c2eef5eab59effbcc5b04ff480977edc530fba"}, + {file = "openai-1.75.0-py3-none-any.whl", hash = "sha256:fe6f932d2ded3b429ff67cc9ad118c71327db32eb9d32dd723de3acfca337125"}, + {file = "openai-1.75.0.tar.gz", hash = "sha256:fb3ea907efbdb1bcfd0c44507ad9c961afd7dce3147292b54505ecfd17be8fd1"}, ] [package.dependencies] @@ -1619,18 +1574,18 @@ typing-extensions = ">=4.11,<5" [package.extras] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -realtime = ["websockets (>=13,<15)"] +realtime = ["websockets (>=13,<16)"] +voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -1639,7 +1594,6 @@ version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1655,7 +1609,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1667,7 +1620,6 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1682,7 +1634,6 @@ version = "1.12.1.2" description = "Query metadata from sdists / bdists / installed packages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, @@ -1693,20 +1644,19 @@ testing = ["pytest", "pytest-cov", "wheel"] [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -1714,7 +1664,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1730,7 +1679,6 @@ version = "1.8.5" description = "Python dependency management and packaging made easy." optional = false python-versions = "<4.0,>=3.8" -groups = ["dev"] files = [ {file = "poetry-1.8.5-py3-none-any.whl", hash = "sha256:5505fba69bf2a792b5d7402d21839c853644337392b745109b86a23010cce5f3"}, {file = "poetry-1.8.5.tar.gz", hash = "sha256:eb2c88d224f58f36df8f7b36d6c380c07d1001bca28bde620f68fc086e881b70"}, @@ -1768,7 +1716,6 @@ version = "1.9.1" description = "Poetry PEP 517 Build Backend" optional = false python-versions = "<4.0,>=3.8" -groups = ["dev"] files = [ {file = "poetry_core-1.9.1-py3-none-any.whl", hash = "sha256:6f45dd3598e0de8d9b0367360253d4c5d4d0110c8f5c71120a14f0e0f116c1a0"}, {file = "poetry_core-1.9.1.tar.gz", hash = "sha256:7a2d49214bf58b4f17f99d6891d947a9836c9899a67a5069f52d7b67217f61b8"}, @@ -1780,7 +1727,6 @@ version = "1.8.0" description = "Poetry plugin to export the dependencies to various formats" optional = false python-versions = "<4.0,>=3.8" -groups = ["dev"] files = [ {file = "poetry_plugin_export-1.8.0-py3-none-any.whl", hash = "sha256:adbe232cfa0cc04991ea3680c865cf748bff27593b9abcb1f35fb50ed7ba2c22"}, {file = "poetry_plugin_export-1.8.0.tar.gz", hash = "sha256:1fa6168a85d59395d835ca564bc19862a7c76061e60c3e7dfaec70d50937fc61"}, @@ -1796,7 +1742,6 @@ version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, @@ -1811,14 +1756,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.50" +version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.8.0" -groups = ["dev"] +python-versions = ">=3.8" files = [ - {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, - {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] @@ -1830,7 +1774,6 @@ version = "7.0.0" description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, @@ -1854,7 +1797,6 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1866,7 +1808,6 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -1877,14 +1818,13 @@ tests = ["pytest"] [[package]] name = "pycodestyle" -version = "2.12.1" +version = "2.13.0" description = "Python style guide checker" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, - {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, + {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, + {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, ] [[package]] @@ -1893,8 +1833,6 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\" or sys_platform == \"darwin\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1902,133 +1840,131 @@ files = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.3" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, + {file = "pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f"}, + {file = "pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" +pydantic-core = "2.33.1" typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.1" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +python-versions = ">=3.9" +files = [ + {file = "pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26"}, + {file = "pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89"}, + {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde"}, + {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65"}, + {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc"}, + {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091"}, + {file = "pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383"}, + {file = "pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504"}, + {file = "pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24"}, + {file = "pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f"}, + {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77"}, + {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961"}, + {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1"}, + {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c"}, + {file = "pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896"}, + {file = "pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83"}, + {file = "pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89"}, + {file = "pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8"}, + {file = "pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d"}, + {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b"}, + {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39"}, + {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a"}, + {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db"}, + {file = "pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda"}, + {file = "pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4"}, + {file = "pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea"}, + {file = "pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a"}, + {file = "pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d"}, + {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4"}, + {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde"}, + {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e"}, + {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd"}, + {file = "pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f"}, + {file = "pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40"}, + {file = "pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523"}, + {file = "pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d"}, + {file = "pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c"}, + {file = "pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18"}, + {file = "pydantic_core-2.33.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb"}, + {file = "pydantic_core-2.33.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5"}, + {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6"}, + {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d"}, + {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96"}, + {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599"}, + {file = "pydantic_core-2.33.1-cp39-cp39-win32.whl", hash = "sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5"}, + {file = "pydantic_core-2.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add"}, + {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544"}, + {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672"}, + {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3"}, + {file = "pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df"}, ] [package.dependencies] @@ -2036,14 +1972,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyflakes" -version = "3.2.0" +version = "3.3.2" description = "passive checker of Python programs" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, - {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, + {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, + {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, ] [[package]] @@ -2052,7 +1987,6 @@ version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -2067,7 +2001,6 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -2079,7 +2012,6 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -2102,7 +2034,6 @@ version = "3.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, @@ -2121,7 +2052,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2132,14 +2062,13 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.8" -groups = ["dev"] +python-versions = ">=3.9" files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, + {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, + {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, ] [package.extras] @@ -2147,31 +2076,27 @@ cli = ["click (>=5.0)"] [[package]] name = "pywin32" -version = "308" +version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" -groups = ["dev"] -markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" -files = [ - {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, - {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, - {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, - {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, - {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, - {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, - {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, - {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, - {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, - {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, - {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, - {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, - {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, - {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, - {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, - {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, - {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, - {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, +files = [ + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] [[package]] @@ -2180,8 +2105,6 @@ version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, @@ -2193,7 +2116,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2252,121 +2174,104 @@ files = [ [[package]] name = "pyzmq" -version = "26.2.1" +version = "26.4.0" description = "Python bindings for 0MQ" optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pyzmq-26.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb"}, - {file = "pyzmq-26.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459"}, - {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c"}, - {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e"}, - {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3"}, - {file = "pyzmq-26.2.1-cp310-cp310-win32.whl", hash = "sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa"}, - {file = "pyzmq-26.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473"}, - {file = "pyzmq-26.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594"}, - {file = "pyzmq-26.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a"}, - {file = "pyzmq-26.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa"}, - {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f"}, - {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba"}, - {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd"}, - {file = "pyzmq-26.2.1-cp311-cp311-win32.whl", hash = "sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7"}, - {file = "pyzmq-26.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1"}, - {file = "pyzmq-26.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7"}, - {file = "pyzmq-26.2.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3"}, - {file = "pyzmq-26.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435"}, - {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a"}, - {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4"}, - {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e"}, - {file = "pyzmq-26.2.1-cp312-cp312-win32.whl", hash = "sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a"}, - {file = "pyzmq-26.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13"}, - {file = "pyzmq-26.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5"}, - {file = "pyzmq-26.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23"}, - {file = "pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce"}, - {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a"}, - {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9"}, - {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad"}, - {file = "pyzmq-26.2.1-cp313-cp313-win32.whl", hash = "sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb"}, - {file = "pyzmq-26.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf"}, - {file = "pyzmq-26.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce"}, - {file = "pyzmq-26.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e"}, - {file = "pyzmq-26.2.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e"}, - {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7"}, - {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8"}, - {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460"}, - {file = "pyzmq-26.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3ef584f13820d2629326fe20cc04069c21c5557d84c26e277cfa6235e523b10f"}, - {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:160194d1034902937359c26ccfa4e276abffc94937e73add99d9471e9f555dd6"}, - {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:574b285150afdbf0a0424dddf7ef9a0d183988eb8d22feacb7160f7515e032cb"}, - {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44dba28c34ce527cf687156c81f82bf1e51f047838d5964f6840fd87dfecf9fe"}, - {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9fbdb90b85c7624c304f72ec7854659a3bd901e1c0ffb2363163779181edeb68"}, - {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a7ad34a2921e8f76716dc7205c9bf46a53817e22b9eec2e8a3e08ee4f4a72468"}, - {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:866c12b7c90dd3a86983df7855c6f12f9407c8684db6aa3890fc8027462bda82"}, - {file = "pyzmq-26.2.1-cp37-cp37m-win32.whl", hash = "sha256:eeb37f65350d5c5870517f02f8bbb2ac0fbec7b416c0f4875219fef305a89a45"}, - {file = "pyzmq-26.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4eb3197f694dfb0ee6af29ef14a35f30ae94ff67c02076eef8125e2d98963cd0"}, - {file = "pyzmq-26.2.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:36d4e7307db7c847fe37413f333027d31c11d5e6b3bacbb5022661ac635942ba"}, - {file = "pyzmq-26.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1c6ae0e95d0a4b0cfe30f648a18e764352d5415279bdf34424decb33e79935b8"}, - {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5b4fc44f5360784cc02392f14235049665caaf7c0fe0b04d313e763d3338e463"}, - {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:51431f6b2750eb9b9d2b2952d3cc9b15d0215e1b8f37b7a3239744d9b487325d"}, - {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdbc78ae2065042de48a65f1421b8af6b76a0386bb487b41955818c3c1ce7bed"}, - {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d14f50d61a89b0925e4d97a0beba6053eb98c426c5815d949a43544f05a0c7ec"}, - {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:004837cb958988c75d8042f5dac19a881f3d9b3b75b2f574055e22573745f841"}, - {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b2007f28ce1b8acebdf4812c1aab997a22e57d6a73b5f318b708ef9bcabbe95"}, - {file = "pyzmq-26.2.1-cp38-cp38-win32.whl", hash = "sha256:269c14904da971cb5f013100d1aaedb27c0a246728c341d5d61ddd03f463f2f3"}, - {file = "pyzmq-26.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:31fff709fef3b991cfe7189d2cfe0c413a1d0e82800a182cfa0c2e3668cd450f"}, - {file = "pyzmq-26.2.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a4bffcadfd40660f26d1b3315a6029fd4f8f5bf31a74160b151f5c577b2dc81b"}, - {file = "pyzmq-26.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e76ad4729c2f1cf74b6eb1bdd05f6aba6175999340bd51e6caee49a435a13bf5"}, - {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8b0f5bab40a16e708e78a0c6ee2425d27e1a5d8135c7a203b4e977cee37eb4aa"}, - {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8e47050412f0ad3a9b2287779758073cbf10e460d9f345002d4779e43bb0136"}, - {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f18ce33f422d119b13c1363ed4cce245b342b2c5cbbb76753eabf6aa6f69c7d"}, - {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ceb0d78b7ef106708a7e2c2914afe68efffc0051dc6a731b0dbacd8b4aee6d68"}, - {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ebdd96bd637fd426d60e86a29ec14b8c1ab64b8d972f6a020baf08a30d1cf46"}, - {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03719e424150c6395b9513f53a5faadcc1ce4b92abdf68987f55900462ac7eec"}, - {file = "pyzmq-26.2.1-cp39-cp39-win32.whl", hash = "sha256:ef5479fac31df4b304e96400fc67ff08231873ee3537544aa08c30f9d22fce38"}, - {file = "pyzmq-26.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:f92a002462154c176dac63a8f1f6582ab56eb394ef4914d65a9417f5d9fde218"}, - {file = "pyzmq-26.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:1fd4b3efc6f62199886440d5e27dd3ccbcb98dfddf330e7396f1ff421bfbb3c2"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ee7152f32c88e0e1b5b17beb9f0e2b14454235795ef68c0c120b6d3d23d12833"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:baa1da72aecf6a490b51fba7a51f1ce298a1e0e86d0daef8265c8f8f9848eb77"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:49135bb327fca159262d8fd14aa1f4a919fe071b04ed08db4c7c37d2f0647162"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bacc1a10c150d58e8a9ee2b2037a70f8d903107e0f0b6e079bf494f2d09c091"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:09dac387ce62d69bec3f06d51610ca1d660e7849eb45f68e38e7f5cf1f49cbcb"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70b3a46ecd9296e725ccafc17d732bfc3cdab850b54bd913f843a0a54dfb2c04"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:59660e15c797a3b7a571c39f8e0b62a1f385f98ae277dfe95ca7eaf05b5a0f12"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0f50db737d688e96ad2a083ad2b453e22865e7e19c7f17d17df416e91ddf67eb"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a003200b6cd64e89b5725ff7e284a93ab24fd54bbac8b4fa46b1ed57be693c27"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f9ba5def063243793dec6603ad1392f735255cbc7202a3a484c14f99ec290705"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1238c2448c58b9c8d6565579393148414a42488a5f916b3f322742e561f6ae0d"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eddb3784aed95d07065bcf94d07e8c04024fdb6b2386f08c197dfe6b3528fda"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0f19c2097fffb1d5b07893d75c9ee693e9cbc809235cf3f2267f0ef6b015f24"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0995fd3530f2e89d6b69a2202e340bbada3191014352af978fa795cb7a446331"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7c6160fe513654e65665332740f63de29ce0d165e053c0c14a161fa60dd0da01"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8ec8e3aea6146b761d6c57fcf8f81fcb19f187afecc19bf1701a48db9617a217"}, - {file = "pyzmq-26.2.1.tar.gz", hash = "sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca"}, +python-versions = ">=3.8" +files = [ + {file = "pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918"}, + {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315"}, + {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d52d62edc96787f5c1dfa6c6ccff9b581cfae5a70d94ec4c8da157656c73b5b"}, + {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1410c3a3705db68d11eb2424d75894d41cff2f64d948ffe245dd97a9debfebf4"}, + {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7dacb06a9c83b007cc01e8e5277f94c95c453c5851aac5e83efe93e72226353f"}, + {file = "pyzmq-26.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6bab961c8c9b3a4dc94d26e9b2cdf84de9918931d01d6ff38c721a83ab3c0ef5"}, + {file = "pyzmq-26.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a5c09413b924d96af2aa8b57e76b9b0058284d60e2fc3730ce0f979031d162a"}, + {file = "pyzmq-26.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7d489ac234d38e57f458fdbd12a996bfe990ac028feaf6f3c1e81ff766513d3b"}, + {file = "pyzmq-26.4.0-cp310-cp310-win32.whl", hash = "sha256:dea1c8db78fb1b4b7dc9f8e213d0af3fc8ecd2c51a1d5a3ca1cde1bda034a980"}, + {file = "pyzmq-26.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:fa59e1f5a224b5e04dc6c101d7186058efa68288c2d714aa12d27603ae93318b"}, + {file = "pyzmq-26.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:a651fe2f447672f4a815e22e74630b6b1ec3a1ab670c95e5e5e28dcd4e69bbb5"}, + {file = "pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54"}, + {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030"}, + {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01"}, + {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e"}, + {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88"}, + {file = "pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6"}, + {file = "pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df"}, + {file = "pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef"}, + {file = "pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca"}, + {file = "pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896"}, + {file = "pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3"}, + {file = "pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b"}, + {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905"}, + {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b"}, + {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63"}, + {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5"}, + {file = "pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b"}, + {file = "pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84"}, + {file = "pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f"}, + {file = "pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44"}, + {file = "pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be"}, + {file = "pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0"}, + {file = "pyzmq-26.4.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:c43fac689880f5174d6fc864857d1247fe5cfa22b09ed058a344ca92bf5301e3"}, + {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902aca7eba477657c5fb81c808318460328758e8367ecdd1964b6330c73cae43"}, + {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e48a830bfd152fe17fbdeaf99ac5271aa4122521bf0d275b6b24e52ef35eb6"}, + {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be2b6de98c824c06f5574331f805707c667dc8f60cb18580b7de078479891e"}, + {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6332452034be001bbf3206ac59c0d2a7713de5f25bb38b06519fc6967b7cf771"}, + {file = "pyzmq-26.4.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:da8c0f5dd352136853e6a09b1b986ee5278dfddfebd30515e16eae425c872b30"}, + {file = "pyzmq-26.4.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f4ccc1a0a2c9806dda2a2dd118a3b7b681e448f3bb354056cad44a65169f6d86"}, + {file = "pyzmq-26.4.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c0b5fceadbab461578daf8d1dcc918ebe7ddd2952f748cf30c7cf2de5d51101"}, + {file = "pyzmq-26.4.0-cp313-cp313-win32.whl", hash = "sha256:28e2b0ff5ba4b3dd11062d905682bad33385cfa3cc03e81abd7f0822263e6637"}, + {file = "pyzmq-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:23ecc9d241004c10e8b4f49d12ac064cd7000e1643343944a10df98e57bc544b"}, + {file = "pyzmq-26.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:1edb0385c7f025045d6e0f759d4d3afe43c17a3d898914ec6582e6f464203c08"}, + {file = "pyzmq-26.4.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:93a29e882b2ba1db86ba5dd5e88e18e0ac6b627026c5cfbec9983422011b82d4"}, + {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45684f276f57110bb89e4300c00f1233ca631f08f5f42528a5c408a79efc4a"}, + {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72073e75260cb301aad4258ad6150fa7f57c719b3f498cb91e31df16784d89b"}, + {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be37e24b13026cfedd233bcbbccd8c0bcd2fdd186216094d095f60076201538d"}, + {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:237b283044934d26f1eeff4075f751b05d2f3ed42a257fc44386d00df6a270cf"}, + {file = "pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b30f862f6768b17040929a68432c8a8be77780317f45a353cb17e423127d250c"}, + {file = "pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c80fcd3504232f13617c6ab501124d373e4895424e65de8b72042333316f64a8"}, + {file = "pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:26a2a7451606b87f67cdeca2c2789d86f605da08b4bd616b1a9981605ca3a364"}, + {file = "pyzmq-26.4.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:831cc53bf6068d46d942af52fa8b0b9d128fb39bcf1f80d468dc9a3ae1da5bfb"}, + {file = "pyzmq-26.4.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:51d18be6193c25bd229524cfac21e39887c8d5e0217b1857998dfbef57c070a4"}, + {file = "pyzmq-26.4.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:445c97854204119ae2232503585ebb4fa7517142f71092cb129e5ee547957a1f"}, + {file = "pyzmq-26.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:807b8f4ad3e6084412c0f3df0613269f552110fa6fb91743e3e306223dbf11a6"}, + {file = "pyzmq-26.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c01d109dd675ac47fa15c0a79d256878d898f90bc10589f808b62d021d2e653c"}, + {file = "pyzmq-26.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0a294026e28679a8dd64c922e59411cb586dad307661b4d8a5c49e7bbca37621"}, + {file = "pyzmq-26.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:22c8dd677274af8dfb1efd05006d6f68fb2f054b17066e308ae20cb3f61028cf"}, + {file = "pyzmq-26.4.0-cp38-cp38-win32.whl", hash = "sha256:14fc678b696bc42c14e2d7f86ac4e97889d5e6b94d366ebcb637a768d2ad01af"}, + {file = "pyzmq-26.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1ef0a536662bbbdc8525f7e2ef19e74123ec9c4578e0582ecd41aedc414a169"}, + {file = "pyzmq-26.4.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a88643de8abd000ce99ca72056a1a2ae15881ee365ecb24dd1d9111e43d57842"}, + {file = "pyzmq-26.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a744ce209ecb557406fb928f3c8c55ce79b16c3eeb682da38ef5059a9af0848"}, + {file = "pyzmq-26.4.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9434540f333332224ecb02ee6278b6c6f11ea1266b48526e73c903119b2f420f"}, + {file = "pyzmq-26.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c6f0a23e55cd38d27d4c89add963294ea091ebcb104d7fdab0f093bc5abb1c"}, + {file = "pyzmq-26.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6145df55dc2309f6ef72d70576dcd5aabb0fd373311613fe85a5e547c722b780"}, + {file = "pyzmq-26.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2ea81823840ef8c56e5d2f9918e4d571236294fea4d1842b302aebffb9e40997"}, + {file = "pyzmq-26.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc2abc385dc37835445abe206524fbc0c9e3fce87631dfaa90918a1ba8f425eb"}, + {file = "pyzmq-26.4.0-cp39-cp39-win32.whl", hash = "sha256:41a2508fe7bed4c76b4cf55aacfb8733926f59d440d9ae2b81ee8220633b4d12"}, + {file = "pyzmq-26.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4000e8255d6cbce38982e5622ebb90823f3409b7ffe8aeae4337ef7d6d2612a"}, + {file = "pyzmq-26.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f6919d9c120488246bdc2a2f96662fa80d67b35bd6d66218f457e722b3ff64"}, + {file = "pyzmq-26.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:98d948288ce893a2edc5ec3c438fe8de2daa5bbbd6e2e865ec5f966e237084ba"}, + {file = "pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9f34f5c9e0203ece706a1003f1492a56c06c0632d86cb77bcfe77b56aacf27b"}, + {file = "pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80c9b48aef586ff8b698359ce22f9508937c799cc1d2c9c2f7c95996f2300c94"}, + {file = "pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f2a5b74009fd50b53b26f65daff23e9853e79aa86e0aa08a53a7628d92d44a"}, + {file = "pyzmq-26.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:61c5f93d7622d84cb3092d7f6398ffc77654c346545313a3737e266fc11a3beb"}, + {file = "pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb"}, + {file = "pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1"}, + {file = "pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494"}, + {file = "pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9"}, + {file = "pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0"}, + {file = "pyzmq-26.4.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:91c3ffaea475ec8bb1a32d77ebc441dcdd13cd3c4c284a6672b92a0f5ade1917"}, + {file = "pyzmq-26.4.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d9a78a52668bf5c9e7b0da36aa5760a9fc3680144e1445d68e98df78a25082ed"}, + {file = "pyzmq-26.4.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b70cab356ff8c860118b89dc86cd910c73ce2127eb986dada4fbac399ef644cf"}, + {file = "pyzmq-26.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acae207d4387780838192326b32d373bb286da0b299e733860e96f80728eb0af"}, + {file = "pyzmq-26.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f928eafd15794aa4be75463d537348b35503c1e014c5b663f206504ec1a90fe4"}, + {file = "pyzmq-26.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:552b0d2e39987733e1e9e948a0ced6ff75e0ea39ab1a1db2fc36eb60fd8760db"}, + {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd670a8aa843f2ee637039bbd412e0d7294a5e588e1ecc9ad98b0cdc050259a4"}, + {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d367b7b775a0e1e54a59a2ba3ed4d5e0a31566af97cc9154e34262777dab95ed"}, + {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112af16c406e4a93df2caef49f884f4c2bb2b558b0b5577ef0b2465d15c1abc"}, + {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76c298683f82669cab0b6da59071f55238c039738297c69f187a542c6d40099"}, + {file = "pyzmq-26.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:49b6ca2e625b46f499fb081aaf7819a177f41eeb555acb05758aa97f4f95d147"}, + {file = "pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d"}, ] [package.dependencies] @@ -2374,100 +2279,105 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "rapidfuzz" -version = "3.12.1" +version = "3.13.0" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "rapidfuzz-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbb7ea2fd786e6d66f225ef6eef1728832314f47e82fee877cb2a793ebda9579"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ae41361de05762c1eaa3955e5355de7c4c6f30d1ef1ea23d29bf738a35809ab"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc3c39e0317e7f68ba01bac056e210dd13c7a0abf823e7b6a5fe7e451ddfc496"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69f2520296f1ae1165b724a3aad28c56fd0ac7dd2e4cff101a5d986e840f02d4"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34dcbf5a7daecebc242f72e2500665f0bde9dd11b779246c6d64d106a7d57c99"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:773ab37fccf6e0513891f8eb4393961ddd1053c6eb7e62eaa876e94668fc6d31"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ecf0e6de84c0bc2c0f48bc03ba23cef2c5f1245db7b26bc860c11c6fd7a097c"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4dc2ebad4adb29d84a661f6a42494df48ad2b72993ff43fad2b9794804f91e45"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8389d98b9f54cb4f8a95f1fa34bf0ceee639e919807bb931ca479c7a5f2930bf"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:165bcdecbfed9978962da1d3ec9c191b2ff9f1ccc2668fbaf0613a975b9aa326"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:129d536740ab0048c1a06ccff73c683f282a2347c68069affae8dbc423a37c50"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b67e390261ffe98ec86c771b89425a78b60ccb610c3b5874660216fcdbded4b"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-win32.whl", hash = "sha256:a66520180d3426b9dc2f8d312f38e19bc1fc5601f374bae5c916f53fa3534a7d"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:82260b20bc7a76556cecb0c063c87dad19246a570425d38f8107b8404ca3ac97"}, - {file = "rapidfuzz-3.12.1-cp310-cp310-win_arm64.whl", hash = "sha256:3a860d103bbb25c69c2e995fdf4fac8cb9f77fb69ec0a00469d7fd87ff148f46"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d9afad7b16d01c9e8929b6a205a18163c7e61b6cd9bcf9c81be77d5afc1067a"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb424ae7240f2d2f7d8dda66a61ebf603f74d92f109452c63b0dbf400204a437"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42149e6d13bd6d06437d2a954dae2184dadbbdec0fdb82dafe92860d99f80519"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:760ac95d788f2964b73da01e0bdffbe1bf2ad8273d0437565ce9092ae6ad1fbc"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cf27e8e4bf7bf9d92ef04f3d2b769e91c3f30ba99208c29f5b41e77271a2614"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00ceb8ff3c44ab0d6014106c71709c85dee9feedd6890eff77c814aa3798952b"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b61c558574fbc093d85940c3264c08c2b857b8916f8e8f222e7b86b0bb7d12"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:346a2d8f17224e99f9ef988606c83d809d5917d17ad00207237e0965e54f9730"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d60d1db1b7e470e71ae096b6456e20ec56b52bde6198e2dbbc5e6769fa6797dc"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2477da227e266f9c712f11393182c69a99d3c8007ea27f68c5afc3faf401cc43"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8499c7d963ddea8adb6cffac2861ee39a1053e22ca8a5ee9de1197f8dc0275a5"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12802e5c4d8ae104fb6efeeb436098325ce0dca33b461c46e8df015c84fbef26"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-win32.whl", hash = "sha256:e1061311d07e7cdcffa92c9b50c2ab4192907e70ca01b2e8e1c0b6b4495faa37"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6e4ed63e204daa863a802eec09feea5448617981ba5d150f843ad8e3ae071a4"}, - {file = "rapidfuzz-3.12.1-cp311-cp311-win_arm64.whl", hash = "sha256:920733a28c3af47870835d59ca9879579f66238f10de91d2b4b3f809d1ebfc5b"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f6235b57ae3faa3f85cb3f90c9fee49b21bd671b76e90fc99e8ca2bdf0b5e4a3"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af4585e5812632c357fee5ab781c29f00cd06bea58f8882ff244cc4906ba6c9e"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5942dc4460e5030c5f9e1d4c9383de2f3564a2503fe25e13e89021bcbfea2f44"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b31ab59e1a0df5afc21f3109b6cfd77b34040dbf54f1bad3989f885cfae1e60"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97c885a7a480b21164f57a706418c9bbc9a496ec6da087e554424358cadde445"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d844c0587d969ce36fbf4b7cbf0860380ffeafc9ac5e17a7cbe8abf528d07bb"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93c95dce8917bf428064c64024de43ffd34ec5949dd4425780c72bd41f9d969"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:834f6113d538af358f39296604a1953e55f8eeffc20cb4caf82250edbb8bf679"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a940aa71a7f37d7f0daac186066bf6668d4d3b7e7ef464cb50bc7ba89eae1f51"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ec9eaf73501c9a7de2c6938cb3050392e2ee0c5ca3921482acf01476b85a7226"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c5ec360694ac14bfaeb6aea95737cf1a6cf805b5fe8ea7fd28814706c7fa838"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b5e176524653ac46f1802bdd273a4b44a5f8d0054ed5013a8e8a4b72f254599"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-win32.whl", hash = "sha256:6f463c6f1c42ec90e45d12a6379e18eddd5cdf74138804d8215619b6f4d31cea"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:b894fa2b30cd6498a29e5c470cb01c6ea898540b7e048a0342775a5000531334"}, - {file = "rapidfuzz-3.12.1-cp312-cp312-win_arm64.whl", hash = "sha256:43bb17056c5d1332f517b888c4e57846c4b5f936ed304917eeb5c9ac85d940d4"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:97f824c15bc6933a31d6e3cbfa90188ba0e5043cf2b6dd342c2b90ee8b3fd47c"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a973b3f5cabf931029a3ae4a0f72e3222e53d412ea85fc37ddc49e1774f00fbf"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7880e012228722dec1be02b9ef3898ed023388b8a24d6fa8213d7581932510"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c78582f50e75e6c2bc38c791ed291cb89cf26a3148c47860c1a04d6e5379c8e"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d7d9e6a04d8344b0198c96394c28874086888d0a2b2f605f30d1b27b9377b7d"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5620001fd4d6644a2f56880388179cc8f3767670f0670160fcb97c3b46c828af"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0666ab4c52e500af7ba5cc17389f5d15c0cdad06412c80312088519fdc25686d"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:27b4d440fa50b50c515a91a01ee17e8ede719dca06eef4c0cccf1a111a4cfad3"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83dccfd5a754f2a0e8555b23dde31f0f7920601bfa807aa76829391ea81e7c67"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b572b634740e047c53743ed27a1bb3b4f93cf4abbac258cd7af377b2c4a9ba5b"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7fa7b81fb52902d5f78dac42b3d6c835a6633b01ddf9b202a3ca8443be4b2d6a"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1d4fbff980cb6baef4ee675963c081f7b5d6580a105d6a4962b20f1f880e1fb"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-win32.whl", hash = "sha256:3fe8da12ea77271097b303fa7624cfaf5afd90261002314e3b0047d36f4afd8d"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:6f7e92fc7d2a7f02e1e01fe4f539324dfab80f27cb70a30dd63a95445566946b"}, - {file = "rapidfuzz-3.12.1-cp313-cp313-win_arm64.whl", hash = "sha256:e31be53d7f4905a6a038296d8b773a79da9ee9f0cd19af9490c5c5a22e37d2e5"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bef5c91d5db776523530073cda5b2a276283258d2f86764be4a008c83caf7acd"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:841e0c2a5fbe8fc8b9b1a56e924c871899932c0ece7fbd970aa1c32bfd12d4bf"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046fc67f3885d94693a2151dd913aaf08b10931639cbb953dfeef3151cb1027c"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4d2d39b2e76c17f92edd6d384dc21fa020871c73251cdfa017149358937a41d"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5857dda85165b986c26a474b22907db6b93932c99397c818bcdec96340a76d5"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c26cd1b9969ea70dbf0dbda3d2b54ab4b2e683d0fd0f17282169a19563efeb1"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf56ea4edd69005786e6c80a9049d95003aeb5798803e7a2906194e7a3cb6472"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fbe7580b5fb2db8ebd53819171ff671124237a55ada3f64d20fc9a149d133960"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:018506a53c3b20dcbda8c93d4484b9eb1764c93d5ea16be103cf6b0d8b11d860"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:325c9c71b737fcd32e2a4e634c430c07dd3d374cfe134eded3fe46e4c6f9bf5d"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:930756639643e3aa02d3136b6fec74e5b9370a24f8796e1065cd8a857a6a6c50"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0acbd27543b158cb915fde03877383816a9e83257832818f1e803bac9b394900"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-win32.whl", hash = "sha256:80ff9283c54d7d29b2d954181e137deee89bec62f4a54675d8b6dbb6b15d3e03"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:fd37e53f0ed239d0cec27b250cec958982a8ba252ce64aa5e6052de3a82fa8db"}, - {file = "rapidfuzz-3.12.1-cp39-cp39-win_arm64.whl", hash = "sha256:4a4422e4f73a579755ab60abccb3ff148b5c224b3c7454a13ca217dfbad54da6"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b7cba636c32a6fc3a402d1cb2c70c6c9f8e6319380aaf15559db09d868a23e56"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b79286738a43e8df8420c4b30a92712dec6247430b130f8e015c3a78b6d61ac2"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dc1937198e7ff67e217e60bfa339f05da268d91bb15fec710452d11fe2fdf60"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b85817a57cf8db32dd5d2d66ccfba656d299b09eaf86234295f89f91be1a0db2"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04283c6f3e79f13a784f844cd5b1df4f518ad0f70c789aea733d106c26e1b4fb"}, - {file = "rapidfuzz-3.12.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a718f740553aad5f4daef790191511da9c6eae893ee1fc2677627e4b624ae2db"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cbdf145c7e4ebf2e81c794ed7a582c4acad19e886d5ad6676086369bd6760753"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0d03ad14a26a477be221fddc002954ae68a9e2402b9d85433f2d0a6af01aa2bb"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1187aeae9c89e838d2a0a2b954b4052e4897e5f62e5794ef42527bf039d469e"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd47dfb1bca9673a48b923b3d988b7668ee8efd0562027f58b0f2b7abf27144c"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187cdb402e223264eebed2fe671e367e636a499a7a9c82090b8d4b75aa416c2a"}, - {file = "rapidfuzz-3.12.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6899b41bf6c30282179f77096c1939f1454836440a8ab05b48ebf7026a3b590"}, - {file = "rapidfuzz-3.12.1.tar.gz", hash = "sha256:6a98bbca18b4a37adddf2d8201856441c26e9c981d8895491b5bc857b5f780eb"}, +files = [ + {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc64da907114d7a18b5e589057e3acaf2fec723d31c49e13fedf043592a3f6a7"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d9d7f84c8e992a8dbe5a3fdbea73d733da39bf464e62c912ac3ceba9c0cff93"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a79a2f07786a2070669b4b8e45bd96a01c788e7a3c218f531f3947878e0f956"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f338e71c45b69a482de8b11bf4a029993230760120c8c6e7c9b71760b6825a1"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb40ca8ddfcd4edd07b0713a860be32bdf632687f656963bcbce84cea04b8d8"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48719f7dcf62dfb181063b60ee2d0a39d327fa8ad81b05e3e510680c44e1c078"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9327a4577f65fc3fb712e79f78233815b8a1c94433d0c2c9f6bc5953018b3565"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:200030dfc0a1d5d6ac18e993c5097c870c97c41574e67f227300a1fb74457b1d"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cc269e74cad6043cb8a46d0ce580031ab642b5930562c2bb79aa7fbf9c858d26"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e62779c6371bd2b21dbd1fdce89eaec2d93fd98179d36f61130b489f62294a92"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f4797f821dc5d7c2b6fc818b89f8a3f37bcc900dd9e4369e6ebf1e525efce5db"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d21f188f6fe4fbf422e647ae9d5a68671d00218e187f91859c963d0738ccd88c"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-win32.whl", hash = "sha256:45dd4628dd9c21acc5c97627dad0bb791764feea81436fb6e0a06eef4c6dceaa"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:624a108122039af89ddda1a2b7ab2a11abe60c1521956f142f5d11bcd42ef138"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-win_arm64.whl", hash = "sha256:435071fd07a085ecbf4d28702a66fd2e676a03369ee497cc38bcb69a46bc77e2"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ccbd0e7ea1a216315f63ffdc7cd09c55f57851afc8fe59a74184cb7316c0598b"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50856f49a4016ef56edd10caabdaf3608993f9faf1e05c3c7f4beeac46bd12a"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd05336db4d0b8348d7eaaf6fa3c517b11a56abaa5e89470ce1714e73e4aca7"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573ad267eb9b3f6e9b04febce5de55d8538a87c56c64bf8fd2599a48dc9d8b77"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fd1451f87ccb6c2f9d18f6caa483116bbb57b5a55d04d3ddbd7b86f5b14998"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6dd36d4916cf57ddb05286ed40b09d034ca5d4bca85c17be0cb6a21290597d9"}, + {file = "rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8"}, ] [package.extras] @@ -2479,7 +2389,6 @@ version = "44.0" description = "readme_renderer is a library for rendering readme descriptions for Warehouse" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, @@ -2499,7 +2408,6 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -2603,7 +2511,6 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2625,7 +2532,6 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -2640,7 +2546,6 @@ version = "2.0.0" description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, @@ -2651,14 +2556,13 @@ idna2008 = ["idna"] [[package]] name = "rich" -version = "13.9.4" +version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ - {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, - {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, ] [package.dependencies] @@ -2675,8 +2579,6 @@ version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" -groups = ["dev"] -markers = "sys_platform == \"linux\"" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, @@ -2692,7 +2594,6 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -2704,7 +2605,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2716,19 +2616,28 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "soupsieve" +version = "2.7" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, +] + [[package]] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -2748,7 +2657,6 @@ version = "0.9.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382"}, {file = "tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108"}, @@ -2792,27 +2700,26 @@ blobfile = ["blobfile (>=2)"] [[package]] name = "tokenizers" -version = "0.21.0" +version = "0.21.1" description = "" optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2"}, - {file = "tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273"}, - {file = "tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74"}, - {file = "tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff"}, - {file = "tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a"}, - {file = "tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c"}, - {file = "tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4"}, +python-versions = ">=3.9" +files = [ + {file = "tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41"}, + {file = "tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f"}, + {file = "tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3"}, + {file = "tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382"}, + {file = "tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab"}, ] [package.dependencies] @@ -2829,8 +2736,6 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2872,7 +2777,6 @@ version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -2884,7 +2788,6 @@ version = "6.4.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, @@ -2905,7 +2808,6 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2927,7 +2829,6 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -2939,14 +2840,13 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "trove-classifiers" -version = "2025.2.18.16" +version = "2025.4.11.15" description = "Canonical source for classifiers on PyPI (pypi.org)." optional = false python-versions = "*" -groups = ["dev"] files = [ - {file = "trove_classifiers-2025.2.18.16-py3-none-any.whl", hash = "sha256:7f6dfae899f23f04b73bc09e0754d9219a6fc4d6cca6acd62f1850a87ea92262"}, - {file = "trove_classifiers-2025.2.18.16.tar.gz", hash = "sha256:b1ee2e1668589217d4edf506743e28b1834da128f8a122bad522c02d837006e1"}, + {file = "trove_classifiers-2025.4.11.15-py3-none-any.whl", hash = "sha256:e7d98983f004df35293caf954bdfe944b139eb402677a97115450e320f0bd855"}, + {file = "trove_classifiers-2025.4.11.15.tar.gz", hash = "sha256:634728aa6698dc1ae3db161da94d9e4c7597a9a5da2c4410211b36f15fed60fc"}, ] [[package]] @@ -2955,7 +2855,6 @@ version = "5.0.0" description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "twine-5.0.0-py3-none-any.whl", hash = "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0"}, {file = "twine-5.0.0.tar.gz", hash = "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4"}, @@ -2974,45 +2873,55 @@ urllib3 = ">=1.26.0" [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] -markers = {dev = "python_version < \"3.11\""} + +[[package]] +name = "typing-inspection" +version = "0.4.0" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, + {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, + {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.29.2" +version = "20.30.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ - {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, - {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, + {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"}, + {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"}, ] [package.dependencies] @@ -3022,7 +2931,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "wcwidth" @@ -3030,7 +2939,6 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -3042,8 +2950,6 @@ version = "1.1.4" description = "Python wrapper for extended filesystem attributes" optional = false python-versions = ">=3.8" -groups = ["dev"] -markers = "sys_platform == \"darwin\"" files = [ {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"}, {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"}, @@ -3129,21 +3035,20 @@ version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = "^3.9" -content-hash = "0d6587f748072058643c5e9a3cae6db6e58e0e364027262e7c45fdde632c22d6" +content-hash = "8a7ab1739b3561d12df6102bd32722423e742c20db84ec36c3439b48f8ceffb1" diff --git a/pyproject.toml b/pyproject.toml index 738fb9f..c62a6c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.37" +version = "0.1.38" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" @@ -12,6 +12,7 @@ pyyaml = "^6.0.1" openai = "^1.65.1" anthropic = "^0.31.2" httpx = "^0.27.2" +beautifulsoup4 = "^4.13.4" [tool.poetry.dev-dependencies] poetry = "^1.7.1" diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 2434988..afc5ae2 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -32,14 +32,13 @@ def test_web_call(client): prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') - # result = client.call(prompt.id, context, "openai/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - # result = client.call(prompt.id, context, "openai/gpt-4o") + result = client.call(prompt.id, context, "openai/gpt-4o") - # result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") + result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") - result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1") + result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - with open('test_web_call.txt', 'w', encoding="utf-8") as f: - f.write(result.content) + # with open('test_web_call.txt', 'w', encoding="utf-8") as f: + # f.write(result.content) assert result.content \ No newline at end of file From a894e1bdad997e810b40c84469295cdd4957877c Mon Sep 17 00:00:00 2001 From: Artyom Maisiuk Date: Mon, 21 Apr 2025 13:27:37 +0300 Subject: [PATCH 05/34] upd: yaml config --- .github/workflows/run-unit-tests.yaml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index 8eb73b8..a5d0e3e 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -21,6 +21,8 @@ jobs: echo LAMOOM_API_TOKEN=${{ secrets.LAMOOM_API_TOKEN }} >> .env echo NEBIUS_API_KEY=${{ secrets.NEBIUS_API_KEY }} >> .env echo CUSTOM_API_KEY=${{ secrets.CUSTOM_API_KEY }} >> .env + echo GOOGLE_API_KEY=${{ secrets.GOOGLE_API_KEY }} >> .env + echo SEARCH_ENGINE_ID=${{ secrets.SEARCH_ENGINE_ID }} >> .env cat .env - name: Install dependencies @@ -30,15 +32,21 @@ jobs: - name: Install Poetry run: pip install poetry + - name: Cache Poetry Dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + - name: Install Python uses: actions/setup-python@v3 with: python-version: 3.11 - cache: poetry - name: Install Python libraries run: poetry install - name: Run tests with pytest - run: | - poetry run make test + run: poetry run make test \ No newline at end of file From 73e8ba0dcf7db25274be4d0e909e9b9a6700c578 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 23 Apr 2025 15:41:00 -0700 Subject: [PATCH 06/34] Updated streams --- lamoom/ai_models/claude/claude_model.py | 308 +++++++++++------------ lamoom/ai_models/openai/openai_models.py | 73 +++--- lamoom/ai_models/tools.py | 179 ------------- lamoom/prompt/base_prompt.py | 14 ++ lamoom/prompt/lamoom.py | 1 + lamoom/prompt/prompt.py | 5 +- lamoom/responses.py | 18 +- tests/prompts/test_web_call.py | 1 + 8 files changed, 217 insertions(+), 382 deletions(-) delete mode 100644 lamoom/ai_models/tools.py diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index 7d8b523..55302de 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -1,26 +1,19 @@ from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER, AIModel import logging -from lamoom.ai_models.constants import C_200K, C_4K +from lamoom.ai_models.constants import C_4K from lamoom.responses import AIResponse -from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts, parse_tool_call_block, \ - format_tool_result_message - +from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts from enum import Enum -import json import typing as t from dataclasses import dataclass -from lamoom.ai_models.claude.responses import ClaudeAIReponse -from lamoom.ai_models.claude.constants import HAIKU, SONNET, OPUS from lamoom.ai_models.utils import get_common_args -from openai.types.chat import ChatCompletionMessage as Message -from lamoom.responses import Prompt from lamoom.exceptions import RetryableCustomError, ConnectionLostError import anthropic -from anthropic.types import ToolParam, ToolUseBlock, ToolResultBlockParam, ContentBlock, MessageParam +from lamoom.ai_models.tools.base_tool_handler import BaseToolHandler logger = logging.getLogger(__name__) @@ -38,6 +31,7 @@ class ClaudeAIModel(AIModel): api_key: str = None provider: AI_MODELS_PROVIDER = AI_MODELS_PROVIDER.CLAUDE family: str = None + tool_handler: BaseToolHandler = None def __post_init__(self): if HAIKU in self.model: @@ -52,6 +46,9 @@ def __post_init__(self): ) self.family = FamilyModel.opus.value + # Initialize tool handler + self.tool_handler = BaseToolHandler(tool_registry=AVAILABLE_TOOLS_REGISTRY) + logger.debug(f"Initialized ClaudeAIModel: {self}") def get_client(self, client_secrets: dict) -> anthropic.Anthropic: @@ -70,12 +67,130 @@ def uny_all_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict result[-1]["content"] += message.get("content") return result + def _process_stream_response(self, + client: anthropic.Anthropic, + current_messages_history: t.List[dict], + max_tokens: int, + system_prompt: t.Optional[str], + stream_function: t.Callable, + check_connection: t.Callable, + stream_params: dict, + mcp_call_registry: t.Dict[str, t.Callable]) -> t.Tuple[str, str, bool]: + """Process a single streaming response from Claude. + + Args: + client: Anthropic client instance + current_messages_history: Current conversation history + max_tokens: Maximum tokens to generate + system_prompt: Optional system prompt + stream_function: Function to call for each text chunk + check_connection: Function to check connection status + stream_params: Parameters for stream function + mcp_call_registry: Registry of available MCP functions + + Returns: + Tuple of (content, stop_reason, has_tool_call) + """ + current_stream_part_content = "" + stream_stop_reason = None + has_tool_call = False + + call_kwargs = { + "model": self.model, + "max_tokens": max_tokens, + "messages": current_messages_history, + } + + if system_prompt: + call_kwargs["system"] = system_prompt + + stream_idx = 0 + try: + logger.debug(f"Initiating Claude stream. History length: {len(current_messages_history)}") + with client.messages.stream(**call_kwargs) as stream_handler: + for text_chunk in stream_handler.text_stream: + stream_idx += 1 + if stream_idx % 5 == 0 and not check_connection(**stream_params): + raise ConnectionLostError("Connection was lost!") + + current_stream_part_content += text_chunk + if text_chunk: + stream_function(text_chunk, **stream_params) + + # Check for tool call after each chunk + detected_tool_call = self.tool_handler.handle_tool_call( + current_stream_part_content + ) + if detected_tool_call: + break # Stop streaming when tool call is detected + + if not detected_tool_call: + final_message_status = stream_handler.get_final_message() + stream_stop_reason = final_message_status.stop_reason + logger.debug(f"Claude stream part finished. Stop Reason: {stream_stop_reason}") + + else: + current_messages_history.append( + {"role": "assistant", "message": current_stream_part_content + detected_tool_call.execution_result} + ) + + except ConnectionLostError: + raise + except anthropic.APIError as e: + logger.exception("[CLAUDEAI] API Error during stream processing", exc_info=e) + raise RetryableCustomError(f"Claude AI API Error: {e}") from e + except Exception as e: + logger.exception("Exception during Claude stream processing", exc_info=e) + raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e + + return current_stream_part_content, stream_stop_reason, has_tool_call + + def _process_non_stream_response(self, + client: anthropic.Anthropic, + current_messages_history: t.List[dict], + max_tokens: int, + system_prompt: t.Optional[str]) -> str: + """Process a non-streaming response from Claude. + + Args: + client: Anthropic client instance + current_messages_history: Current conversation history + max_tokens: Maximum tokens to generate + system_prompt: Optional system prompt + + Returns: + Final response content + """ + call_kwargs = { + "model": self.model, + "messages": current_messages_history, + "max_tokens": max_tokens, + } + + if system_prompt: + call_kwargs['system'] = system_prompt + + logger.debug(f"Calling Claude API with messages: {current_messages_history}") + response = client.messages.create(**call_kwargs) + + text_blocks = [block.text for block in response.content if block.type == "text"] + content = "\n".join(text_blocks).strip() + + # Check for tool call + content, has_tool_call = self.tool_handler.handle_tool_call(content) + + if has_tool_call: + # Recursively process the next response + return self._process_non_stream_response(client, current_messages_history, max_tokens, system_prompt) + + return content def call(self, messages: t.List[dict], max_tokens: int, client_secrets: dict = {}, - tool_registry: t.Dict[str, ToolDefinition] = AVAILABLE_TOOLS_REGISTRY, + tool_registry: t.Dict[str, ToolDefinition] = {}, + mcp_call_registry: t.Dict[str, t.Callable] = None, max_tool_iterations: int = 5, # Safety limit for sequential calls **kwargs) -> AIResponse: max_tokens = min(max_tokens, self.max_tokens) @@ -86,12 +201,13 @@ def call(self, **self.get_params(), **kwargs, } - messages = self.uny_all_messages_with_same_role(messages) - tool_definitions = list(tool_registry.values()) + # Update tool handler with MCP registry + if mcp_call_registry: + self.tool_handler.mcp_call_registry = mcp_call_registry # Inject Tool Prompts into initial messages - current_messages_history = inject_tool_prompts(messages, tool_definitions) + current_messages_history = inject_tool_prompts(messages, list(tool_registry.values())) system_prompt = None if current_messages_history and current_messages_history[0].get('role') == "system": @@ -116,168 +232,37 @@ def call(self, iteration_count += 1 logger.info(f"--- Custom Claude Tool Streaming Iteration: {iteration_count} ---") - current_stream_part_content = "" - stream_stop_reason = None # Reason for this specific stream part - - call_kwargs = { - "model": self.model, - "max_tokens": max_tokens, - "messages": current_messages_history, # Use current history - } - - if system_prompt: - call_kwargs["system"] = system_prompt - - stream_idx = 0 - try: - logger.debug(f"Initiating Claude stream. History length: {len(current_messages_history)}") - with client.messages.stream(**call_kwargs) as stream_handler: - for text_chunk in stream_handler.text_stream: - stream_idx += 1 - # Check connection periodically - if stream_idx % 5 == 0: - if not check_connection(**stream_params): - raise ConnectionLostError("Connection was lost!") - # Accumulate content - current_stream_part_content += text_chunk - if text_chunk: - stream_function(text_chunk, **stream_params) - - # Get final message details after stream ends - final_message_status = stream_handler.get_final_message() - stream_stop_reason = final_message_status.stop_reason - logger.debug(f"Claude stream part finished. Stop Reason: {stream_stop_reason}") - - except ConnectionLostError: # Catch specifically - raise # Re-raise immediately - except anthropic.APIError as e: - logger.exception("[CLAUDEAI] API Error during stream processing", exc_info=e) - raise RetryableCustomError(f"Claude AI API Error: {e}") from e - except Exception as e: - logger.exception("Exception during Claude stream processing", exc_info=e) - raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e - - # --- After processing stream part --- - logger.debug(f"Accumulated stream content for parsing: {current_stream_part_content[:500]}...") + current_stream_part_content, stream_stop_reason, has_tool_call = self._process_stream_response( + client, current_messages_history, max_tokens, system_prompt, + stream_function, check_connection, stream_params, mcp_call_registry + ) # Add the raw assistant response to history *before* parsing assistant_message_to_add = {"role": "assistant", "content": current_stream_part_content} current_messages_history.append(assistant_message_to_add) - # Parse the accumulated text for the custom tool block - parsed_tool_call = parse_tool_call_block(current_stream_part_content) - - if parsed_tool_call: - tool_name = parsed_tool_call.get("tool_name") - parameters = parsed_tool_call.get("parameters", {}) - logger.info(f"Custom tool call block parsed: {tool_name}") - - tool_definition = tool_registry.get(tool_name) - tool_result_str = "" # Initialize - - # Execute the tool - if tool_definition and tool_definition.execution_function: - try: - logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") - tool_result_str = tool_definition.execution_function(**parameters) - logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") - except Exception as exec_err: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) - tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) - else: - logger.warning(f"Tool '{tool_name}' requested but not found/executable.") - tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) - - tool_result_message = format_tool_result_message(tool_name, tool_result_str) - current_messages_history.append(tool_result_message) - + if has_tool_call: continue # --- No Tool Call Found in this stream part --- - else: - logger.info("No custom tool call block found in this stream part. Finishing.") - content = current_stream_part_content # Final content is from this last part + content += current_stream_part_content + if stream_stop_reason == "end_turn": break - # --- End of streaming while loop --- - if iteration_count >= max_tool_iterations: - logger.warning(f"Reached max tool call iterations ({max_tool_iterations}) during custom Claude streaming.") - # Use content from the last attempt as final content - content = current_stream_part_content else: - iteration_count = 0 - while iteration_count < max_tool_iterations: - call_kwargs = { - "model": self.model, - "messages": current_messages_history, - "max_tokens": max_tokens, - } - - if system_prompt: - call_kwargs['system'] = system_prompt - - logger.debug(f"Calling Claude API with messages: {current_messages_history}") - - response = client.messages.create(**call_kwargs) - # *** TOOL CALL CHECK *** - response_text = response.content[0].text if response.content else "" - - # Parse the response for the block - parsed_tool_call = parse_tool_call_block(response_text) - if parsed_tool_call: - tool_name = parsed_tool_call.get("tool_name") - parameters = parsed_tool_call.get("parameters", {}) - - if response: - assistant_message_to_add = {"role": response.role, "content": response.content} - else: - assistant_message_to_add = {"role": "assistant", "content": response_text} - current_messages_history.append(assistant_message_to_add) - - tool_definition = tool_registry.get(tool_name) - - # Execute the tool - if tool_definition and tool_definition.execution_function: - try: - logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") - tool_result_str = tool_definition.execution_function(**parameters) - logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") - except Exception as exec_err: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) - tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) - else: - logger.warning(f"Tool '{tool_name}' requested but not found in registry or not executable.") - tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) - - # Add the tool result to history - tool_result_message = format_tool_result_message(tool_name, tool_result_str) - current_messages_history.append(tool_result_message) - - continue - else: - text_blocks = [block.text for block in response.content if block.type == "text"] - final_response_content = "\n".join(text_blocks).strip() - content = final_response_content - break - - return ClaudeAIReponse( - message=Message(content=content, role="assistant"), - content=content, - prompt=Prompt( - messages=kwargs.get("messages"), - functions=kwargs.get("tools"), - max_tokens=max_tokens, - temperature=kwargs.get("temperature"), - top_p=kwargs.get("top_p"), - ), - ) + content = self._process_non_stream_response( + client, current_messages_history, max_tokens, system_prompt + ) + except Exception as e: - logger.exception("[CLAUDEAI] failed to handle chat stream", exc_info=e) - raise RetryableCustomError(f"Claude AI call failed!") + logger.exception("Exception during Claude API call", exc_info=e) + raise RetryableCustomError(f"Claude AI API call failed: {e}") from e + + return AIResponse(content=content) @property def name(self) -> str: - return self.model + return f"Claude {self.family}" def get_params(self) -> t.Dict[str, t.Any]: return { @@ -288,5 +273,6 @@ def get_params(self) -> t.Dict[str, t.Any]: def get_metrics_data(self) -> t.Dict[str, t.Any]: return { "model": self.model, + "family": self.family, "max_tokens": self.max_tokens, } diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 8e71201..d5797e8 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -132,6 +132,7 @@ def call_chat_completion( max_tokens: t.Optional[int], functions: t.List[t.Dict[str, str]] = [], tool_registry: t.Dict[str, ToolDefinition] = AVAILABLE_TOOLS_REGISTRY, + mcp_call_registry: t.Dict[str, t.Callable] = None, max_tool_iterations: int = 5, # Safety limit for sequential calls stream_function: t.Callable = None, check_connection: t.Callable = None, @@ -165,9 +166,7 @@ def call_chat_completion( **call_kwargs, ) - #TODO: handle streaming part later if kwargs.get("stream"): - return OpenAIStreamResponse( stream_function=stream_function, check_connection=check_connection, @@ -176,8 +175,9 @@ def call_chat_completion( client=client, initial_call_kwargs=call_kwargs, tool_registry=tool_registry, - initial_messages_history=current_messages_history, + mcp_call_registry=mcp_call_registry, max_tool_iterations=max_tool_iterations, + initial_messages_history=current_messages_history, prompt=Prompt( messages=kwargs.get("messages"), functions=kwargs.get("tools"), @@ -207,21 +207,10 @@ def call_chat_completion( assistant_message_to_add = {"role": "assistant", "content": response_text} current_messages_history.append(assistant_message_to_add) - tool_definition = tool_registry.get(tool_name) - - # Execute the tool - if tool_definition and tool_definition.execution_function: - try: - logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") - tool_result_str = tool_definition.execution_function(**parameters) - logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") - except Exception as exec_err: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) - tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) - else: - logger.warning(f"Tool '{tool_name}' requested but not found in registry or not executable.") - tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) - + # Execute the tool and get result + tool_result_str = self._handle_tool_call(tool_name, parameters, mcp_call_registry) + + # Add tool result to history tool_result_message = format_tool_result_message(tool_name, tool_result_str) current_messages_history.append(tool_result_message) @@ -256,6 +245,7 @@ class OpenAIStreamResponse(OpenAIResponse): client: OpenAI tool_registry: t.Dict[str, ToolDefinition] + mcp_call_registry: t.Dict[str, t.Callable] max_tool_iterations: int initial_call_kwargs: dict # Original non-message kwargs initial_messages_history: t.List[dict] # Original messages list *with tool prompts injected* @@ -275,6 +265,32 @@ def process_message(self, text: str, idx: int): return self.stream_function(text, **self.stream_params) + def _handle_tool_call(self, + tool_name: str, + parameters: dict) -> str: + """Handle a tool call by executing the corresponding function from the MCP registry. + + Args: + tool_name: Name of the tool to execute + parameters: Parameters for the tool + + Returns: + String representation of the tool result + """ + tool_function = self.mcp_call_registry.get(tool_name) + if not tool_function: + logger.warning(f"Tool '{tool_name}' not found in MCP registry") + return json.dumps({"error": f"Tool '{tool_name}' is not available."}) + + try: + logger.info(f"Executing MCP tool '{tool_name}' with parameters: {parameters}") + result = tool_function(**parameters) + logger.info(f"MCP tool '{tool_name}' executed successfully") + return json.dumps({"result": result}) + except Exception as e: + logger.exception(f"Error executing MCP tool '{tool_name}'", exc_info=e) + return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) + def stream(self): """ Processes the stream, parses for custom blocks, @@ -329,23 +345,10 @@ def stream(self): parameters = parsed_tool_call.get("parameters", {}) logger.info(f"Custom tool call block parsed: {tool_name}") - tool_definition = self.tool_registry.get(tool_name) - tool_result_str = "" - - # Execute the tool - if tool_definition and tool_definition.execution_function: - try: - logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") - # *** EXECUTE TOOL *** - tool_result_str = tool_definition.execution_function(**parameters) - logger.info(f"Tool '{tool_name}' executed. Result snippet: {tool_result_str[:200]}...") - except Exception as exec_err: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=exec_err) - tool_result_str = json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(exec_err)}"}) - else: - logger.warning(f"Tool '{tool_name}' requested but not found in registry or not executable.") - tool_result_str = json.dumps({"error": f"Tool '{tool_name}' is not available."}) - + # Execute the tool and get result + tool_result_str = self._handle_tool_call(tool_name, parameters) + + # Add tool result to history tool_result_message = format_tool_result_message(tool_name, tool_result_str) self._current_messages_history.append(tool_result_message) diff --git a/lamoom/ai_models/tools.py b/lamoom/ai_models/tools.py deleted file mode 100644 index 453b262..0000000 --- a/lamoom/ai_models/tools.py +++ /dev/null @@ -1,179 +0,0 @@ -from dataclasses import dataclass, field -import typing as t -import os -import json -import re -import logging -from bs4 import BeautifulSoup -import requests -from dotenv import load_dotenv - -load_dotenv() -logger = logging.getLogger(__name__) - -API_KEY = os.getenv("GOOGLE_API_KEY") -SEARCH_ID = os.getenv("SEARCH_ENGINE_ID") - -# --- Constants for Prompting --- -TOOL_CALL_START_TAG = "" -TOOL_CALL_END_TAG = "" -TOOL_PROMPT_HEADER = """ -You have the following skills available:""" - -TOOL_PROMPT_FOOTER = f""" -If you determine you need to use one of your skills to fulfill the user's request, you MUST format your request as follows, replacing the placeholders: -{TOOL_CALL_START_TAG} -{{ - "tool_name": "", - "parameters": {{ - "": , - "": - // ... include all required parameters for the chosen tool - }} -}} -{TOOL_CALL_END_TAG} -Only include the {TOOL_CALL_START_TAG}...{TOOL_CALL_END_TAG} block if you need to use a skill. Do not add any other text before or after the block if you decide to use a skill.""" - -@dataclass -class ToolParameter: - name: str - type: str - description: str - required: bool = True - -@dataclass -class ToolDefinition: - name: str - description: str - parameters: t.List[ToolParameter] - execution_function: t.Callable - - -def format_tool_description(tool: ToolDefinition) -> str: - """Formats a single tool's description for the prompt.""" - param_desc = ", ".join([f"{p.name}: {p.type} ({p.description})" for p in tool.parameters]) - return f"- {tool.name}({{{param_desc}}}) - {tool.description}" - -def inject_tool_prompts( - messages: t.List[dict], - available_tools: t.List[ToolDefinition] - ) -> t.List[dict]: - """Injects tool descriptions and usage instructions into the system prompt.""" - if not available_tools: - return messages - - tool_descriptions = "\n".join([format_tool_description(tool) for tool in available_tools]) - tool_system_prompt = f"{TOOL_PROMPT_HEADER}\n{tool_descriptions}\n{TOOL_PROMPT_FOOTER}" - - # Find system prompt or prepend to user prompt - modified_messages = list(messages) # Create a copy - found_system = False - for i, msg in enumerate(modified_messages): - if msg.get("role") == "system": - # Append to existing system prompt - modified_messages[i]["content"] = f"{msg.get('content', '')}\n\n{tool_system_prompt}" - found_system = True - break - - if not found_system: - # Prepend a new system message - modified_messages.insert(0, {"role": "system", "content": tool_system_prompt}) - - logger.debug(f"Injected tool system prompt:\n{tool_system_prompt}") - return modified_messages - -def parse_tool_call_block(text_response: str) -> t.Optional[t.Dict[str, t.Any]]: - """ - Parses the block from the model's text response using regex. - Returns the parsed JSON content as a dict, or None if not found or invalid. - """ - if not text_response: - return None - - # Regex to find the block, allowing for whitespace variations - # DOTALL allows '.' to match newlines within the JSON block - match = re.search( - rf"{re.escape(TOOL_CALL_START_TAG)}(.*?){re.escape(TOOL_CALL_END_TAG)}", - text_response, - re.DOTALL | re.IGNORECASE - ) - - if not match: - return None - - json_content = match.group(1).strip() - logger.debug(f"Found potential tool call JSON block: {json_content}") - - try: - parsed_data = json.loads(json_content) - # Basic validation - if "tool_name" in parsed_data and "parameters" in parsed_data and isinstance(parsed_data["parameters"], dict): - logger.info(f"Successfully parsed tool call: {parsed_data['tool_name']}") - return parsed_data - else: - logger.warning(f"Parsed JSON block lacks required 'tool_name' or 'parameters': {json_content}") - return None - except json.JSONDecodeError as e: - logger.error(f"Failed to decode JSON from tool call block: {json_content}", exc_info=e) - return None - -def format_tool_result_message(tool_name: str, tool_result: str) -> dict: - """Formats the tool execution result into a message for the history.""" - return { - "role": "user", - "content": f"Tool execution result for '{tool_name}':\n{tool_result}" - } - -def scrape_webpage(url: str): - """ - Scrapes the content of a webpage and returns the text. - """ - if not url.startswith(("https://", "http://")): - url = "https://" + url - response = requests.get(url) - - if response.status_code == 200: - soup = BeautifulSoup(response.content, "html.parser") - text = soup.get_text() - clean_text = text.splitlines() - clean_text = [element.strip() - for element in clean_text if element.strip()] - clean_text = '\n'.join(clean_text) - return clean_text - - else: - return "Failed to retrieve the website content." - - -def perform_web_search(query: str): - url = "https://www.googleapis.com/customsearch/v1" - params = { - 'q': query, - 'key': API_KEY, - 'cx': SEARCH_ID, - 'num': 3 - } - - response = requests.get(url, params=params) - results = response.json() - - search_result = "" - - if 'items' in results: - for result in results['items']: - search_result += scrape_webpage(result['link']) + '\n' - - return search_result - -web_call_tool = ToolDefinition( - name="web_call", - description="Performs a web search using a search engine to find up-to-date information or details not present in the internal knowledge.", - parameters=[ - ToolParameter(name="query", type="string", description="The search query to use.", required=True) - ], - execution_function=perform_web_search -) - -AVAILABLE_TOOLS_REGISTRY: t.Dict[str, ToolDefinition] = { - "web_call": web_call_tool, -} \ No newline at end of file diff --git a/lamoom/prompt/base_prompt.py b/lamoom/prompt/base_prompt.py index ae6d9cd..ecb0ab1 100644 --- a/lamoom/prompt/base_prompt.py +++ b/lamoom/prompt/base_prompt.py @@ -3,6 +3,7 @@ from collections import defaultdict from dataclasses import dataclass, field +from lamoom.ai_models.tools.base_tool import ToolDefinition from lamoom.prompt.chat import ChatsEntity logger = logging.getLogger(__name__) @@ -18,6 +19,8 @@ class BasePrompt: functions: t.List[dict] = None top_p: float = 0.0 temperature: float = 0.0 + # Add tool registry + tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) def get_params(self): return { @@ -72,3 +75,14 @@ def add_function(self, function: dict): if not self.functions: self.functions = [] self.functions.append(function) + + def add_tool( + self, tool: ToolDefinition + ): + self.tool_registry[tool.name] = tool + + def add_tools( + self, tools: list[ToolDefinition] + ): + for tool in tools: + self.tool_registry[tool.name] = tool diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 157806a..653f0c7 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -258,6 +258,7 @@ def call( result = current_attempt.ai_model.call( calling_messages.get_messages(), calling_messages.max_sample_budget, + tool_registry=prompt.tool_registry, stream_function=stream_function, check_connection=check_connection, stream_params=stream_params, diff --git a/lamoom/prompt/prompt.py b/lamoom/prompt/prompt.py index 0ce3854..5858b5c 100644 --- a/lamoom/prompt/prompt.py +++ b/lamoom/prompt/prompt.py @@ -1,9 +1,12 @@ +from dataclasses import dataclass, field import logging from copy import deepcopy -from dataclasses import dataclass + +import typing as t from lamoom import settings from lamoom.ai_models.attempt_to_call import AttemptToCall +from lamoom.ai_models.tools.base_tool import ToolDefinition from lamoom.prompt.base_prompt import BasePrompt from lamoom.prompt.chat import ChatsEntity from lamoom.prompt.user_prompt import UserPrompt diff --git a/lamoom/responses.py b/lamoom/responses.py index 5abcb79..26866e3 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -8,12 +8,18 @@ @dataclass -class Prompt: - messages: dict = None - functions: dict = None - max_tokens: int = 0 - temperature: Decimal = Decimal(0.0) - top_p: Decimal = Decimal(0.0) +class Prompt(BasePrompt): + id: str = None + max_tokens: int = None + min_sample_tokens: int = settings.DEFAULT_SAMPLE_MIN_BUDGET + reserved_tokens_budget_for_sampling: int = None + version: str = None + # Add tool registry + tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) + + def add_tool(self, tool: ToolDefinition): + """Add a tool to this prompt's registry""" + self.tool_registry[tool.name] = tool @dataclass diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index afc5ae2..39bb835 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -31,6 +31,7 @@ def test_web_call(client): client.service.clear_cache() prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') + prompt.add_tool(WEB_SEARCH_TOOL) result = client.call(prompt.id, context, "openai/gpt-4o") From 81a896da0c28da9349f2d96a9725842ea8ed5fb3 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Sun, 4 May 2025 17:09:41 -0700 Subject: [PATCH 07/34] Add tool calls --- README.md | 11 +- lamoom/ai_models/claude/claude_model.py | 126 ++++------- lamoom/ai_models/openai/openai_models.py | 260 +++++++++-------------- lamoom/prompt/lamoom.py | 7 +- lamoom/responses.py | 22 +- tests/prompts/test_web_call.py | 3 +- 6 files changed, 166 insertions(+), 263 deletions(-) diff --git a/README.md b/README.md index 36fe248..fd5d81d 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,14 @@ client.add_ideal_answer( ) ``` +### To Add Search Credentials: +- Add Search ENgine id from here: +https://programmablesearchengine.google.com/controlpanel/create + +- Get A google Search Key: +https://developers.google.com/custom-search/v1/introduction/?apix=true + + ### Monitoring and Management - **Test Dashboard**: Review created tests and scores at https://cloud.lamoom.com/tests - **Prompt Management**: Update prompts and rerun tests for published or saved versions @@ -219,4 +227,5 @@ We welcome contributions! Please see our Contribution Guidelines for more inform This project is licensed under the Apache2.0 License - see the [LICENSE](LICENSE.txt) file for details. ## Contact -For support or contributions, please contact us via GitHub Issues. \ No newline at end of file +For support or contributions, please contact us via GitHub Issues. + diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index 55302de..f5791ed 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -1,9 +1,10 @@ from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER, AIModel import logging +from lamoom.ai_models.claude.constants import HAIKU, SONNET, OPUS from lamoom.ai_models.constants import C_4K from lamoom.responses import AIResponse -from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts +from lamoom.ai_models.tools.base_tool import ToolDefinition, handle_tool_call, inject_tool_prompts from enum import Enum import typing as t @@ -13,7 +14,6 @@ from lamoom.exceptions import RetryableCustomError, ConnectionLostError import anthropic -from lamoom.ai_models.tools.base_tool_handler import BaseToolHandler logger = logging.getLogger(__name__) @@ -31,7 +31,6 @@ class ClaudeAIModel(AIModel): api_key: str = None provider: AI_MODELS_PROVIDER = AI_MODELS_PROVIDER.CLAUDE family: str = None - tool_handler: BaseToolHandler = None def __post_init__(self): if HAIKU in self.model: @@ -45,10 +44,6 @@ def __post_init__(self): f"Unknown family for {self.model}. Please add it obviously. Setting as Claude 3 Opus" ) self.family = FamilyModel.opus.value - - # Initialize tool handler - self.tool_handler = BaseToolHandler(tool_registry=AVAILABLE_TOOLS_REGISTRY) - logger.debug(f"Initialized ClaudeAIModel: {self}") def get_client(self, client_secrets: dict) -> anthropic.Anthropic: @@ -75,7 +70,7 @@ def _process_stream_response(self, stream_function: t.Callable, check_connection: t.Callable, stream_params: dict, - mcp_call_registry: t.Dict[str, t.Callable]) -> t.Tuple[str, str, bool]: + tool_registry: t.Dict[str, ToolDefinition]) -> t.Tuple[str, str, bool]: """Process a single streaming response from Claude. Args: @@ -93,7 +88,7 @@ def _process_stream_response(self, """ current_stream_part_content = "" stream_stop_reason = None - has_tool_call = False + detected_tool_call = None call_kwargs = { "model": self.model, @@ -108,20 +103,22 @@ def _process_stream_response(self, try: logger.debug(f"Initiating Claude stream. History length: {len(current_messages_history)}") with client.messages.stream(**call_kwargs) as stream_handler: + current_stream_part_content = '' for text_chunk in stream_handler.text_stream: stream_idx += 1 - if stream_idx % 5 == 0 and not check_connection(**stream_params): + if check_connection and stream_idx % 5 == 0 and not check_connection(**stream_params): raise ConnectionLostError("Connection was lost!") current_stream_part_content += text_chunk - if text_chunk: + if stream_function and text_chunk: stream_function(text_chunk, **stream_params) # Check for tool call after each chunk - detected_tool_call = self.tool_handler.handle_tool_call( - current_stream_part_content + detected_tool_call = handle_tool_call( + current_stream_part_content, tool_registry ) if detected_tool_call: + logger.info(f'Found tool {detected_tool_call} in the response') break # Stop streaming when tool call is detected if not detected_tool_call: @@ -131,8 +128,9 @@ def _process_stream_response(self, else: current_messages_history.append( - {"role": "assistant", "message": current_stream_part_content + detected_tool_call.execution_result} + {"role": "assistant", "content": current_stream_part_content + detected_tool_call.execution_result} ) + except ConnectionLostError: raise @@ -143,54 +141,14 @@ def _process_stream_response(self, logger.exception("Exception during Claude stream processing", exc_info=e) raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e - return current_stream_part_content, stream_stop_reason, has_tool_call - - def _process_non_stream_response(self, - client: anthropic.Anthropic, - current_messages_history: t.List[dict], - max_tokens: int, - system_prompt: t.Optional[str]) -> str: - """Process a non-streaming response from Claude. - - Args: - client: Anthropic client instance - current_messages_history: Current conversation history - max_tokens: Maximum tokens to generate - system_prompt: Optional system prompt - - Returns: - Final response content - """ - call_kwargs = { - "model": self.model, - "messages": current_messages_history, - "max_tokens": max_tokens, - } - - if system_prompt: - call_kwargs['system'] = system_prompt + return current_stream_part_content, stream_stop_reason, detected_tool_call - logger.debug(f"Calling Claude API with messages: {current_messages_history}") - response = client.messages.create(**call_kwargs) - - text_blocks = [block.text for block in response.content if block.type == "text"] - content = "\n".join(text_blocks).strip() - - # Check for tool call - content, has_tool_call = self.tool_handler.handle_tool_call(content) - - if has_tool_call: - # Recursively process the next response - return self._process_non_stream_response(client, current_messages_history, max_tokens, system_prompt) - - return content def call(self, messages: t.List[dict], max_tokens: int, client_secrets: dict = {}, tool_registry: t.Dict[str, ToolDefinition] = {}, - mcp_call_registry: t.Dict[str, t.Callable] = None, max_tool_iterations: int = 5, # Safety limit for sequential calls **kwargs) -> AIResponse: max_tokens = min(max_tokens, self.max_tokens) @@ -202,10 +160,6 @@ def call(self, **kwargs, } - # Update tool handler with MCP registry - if mcp_call_registry: - self.tool_handler.mcp_call_registry = mcp_call_registry - # Inject Tool Prompts into initial messages current_messages_history = inject_tool_prompts(messages, list(tool_registry.values())) @@ -224,40 +178,34 @@ def call(self, stream_params = kwargs.get("stream_params") content = "" + iteration_count = 0 + while iteration_count < max_tool_iterations: + iteration_count += 1 + try: + logger.info(f"--- Custom Claude Tool Streaming Iteration: {iteration_count} ---") + + current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( + client, current_messages_history, max_tokens, system_prompt, + stream_function, check_connection, stream_params, tool_registry + ) - try: - if kwargs.get("stream"): - iteration_count = 0 - while iteration_count < max_tool_iterations: - iteration_count += 1 - logger.info(f"--- Custom Claude Tool Streaming Iteration: {iteration_count} ---") - - current_stream_part_content, stream_stop_reason, has_tool_call = self._process_stream_response( - client, current_messages_history, max_tokens, system_prompt, - stream_function, check_connection, stream_params, mcp_call_registry - ) - - # Add the raw assistant response to history *before* parsing - assistant_message_to_add = {"role": "assistant", "content": current_stream_part_content} - current_messages_history.append(assistant_message_to_add) - - if has_tool_call: - continue - - # --- No Tool Call Found in this stream part --- - content += current_stream_part_content - if stream_stop_reason == "end_turn": - break + # Add the raw assistant response to history *before* parsing + assistant_message_to_add = {"role": "assistant", "content": current_stream_part_content} + current_messages_history.append(assistant_message_to_add) - else: - content = self._process_non_stream_response( - client, current_messages_history, max_tokens, system_prompt - ) + if detected_tool_call: + logger.info(f'Found {detected_tool_call}') + continue - except Exception as e: - logger.exception("Exception during Claude API call", exc_info=e) - raise RetryableCustomError(f"Claude AI API call failed: {e}") from e + # --- No Tool Call Found in this stream part --- + content += current_stream_part_content + if stream_stop_reason == "end_turn": + break + except Exception as e: + logger.exception("Exception during Claude API call", exc_info=e) + raise RetryableCustomError(f"Claude AI API call failed: {e}") from e + logger.info(f"Returning: {content}") return AIResponse(content=content) @property diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index d5797e8..fbc49c7 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -11,8 +11,7 @@ from lamoom.ai_models.openai.responses import OpenAIResponse from lamoom.ai_models.utils import get_common_args from lamoom.exceptions import ConnectionLostError -from lamoom.ai_models.tools import ToolDefinition, AVAILABLE_TOOLS_REGISTRY, inject_tool_prompts, parse_tool_call_block, \ - format_tool_result_message +from lamoom.ai_models.tools.base_tool import ToolDefinition, inject_tool_prompts, parse_tool_call_block import json from openai.types.chat import ChatCompletionMessage as Message @@ -131,7 +130,7 @@ def call_chat_completion( messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], functions: t.List[t.Dict[str, str]] = [], - tool_registry: t.Dict[str, ToolDefinition] = AVAILABLE_TOOLS_REGISTRY, + tool_registry: t.Dict[str, ToolDefinition] = {}, mcp_call_registry: t.Dict[str, t.Callable] = None, max_tool_iterations: int = 5, # Safety limit for sequential calls stream_function: t.Callable = None, @@ -165,73 +164,25 @@ def call_chat_completion( result = client.chat.completions.create( **call_kwargs, ) - - if kwargs.get("stream"): - return OpenAIStreamResponse( - stream_function=stream_function, - check_connection=check_connection, - stream_params=stream_params, - original_result=result, - client=client, - initial_call_kwargs=call_kwargs, - tool_registry=tool_registry, - mcp_call_registry=mcp_call_registry, - max_tool_iterations=max_tool_iterations, - initial_messages_history=current_messages_history, - prompt=Prompt( - messages=kwargs.get("messages"), - functions=kwargs.get("tools"), - max_tokens=max_tokens, - temperature=kwargs.get("temperature"), - top_p=kwargs.get("top_p"), - ), - ).stream() - - logger.debug(f"Result: {result.choices[0]}") - choice = result.choices[0] - response_message = choice.message - # *** TOOL CALL CHECK *** - response_text = response_message.content - - # Parse the response for the block - parsed_tool_call = parse_tool_call_block(response_text) - - if parsed_tool_call: - tool_name = parsed_tool_call.get("tool_name") - parameters = parsed_tool_call.get("parameters", {}) - - # Add the assistant's message (containing the tool call) to history - if response_message: - assistant_message_to_add = response_message.__dict__ - else: - assistant_message_to_add = {"role": "assistant", "content": response_text} - current_messages_history.append(assistant_message_to_add) - - # Execute the tool and get result - tool_result_str = self._handle_tool_call(tool_name, parameters, mcp_call_registry) - - # Add tool result to history - tool_result_message = format_tool_result_message(tool_name, tool_result_str) - current_messages_history.append(tool_result_message) - - continue - - # *** NO TOOL CALL - Normal Response *** - else: - logger.info("No tool call block found in response. Finishing.") - return OpenAIResponse( - finish_reason=choice.finish_reason, - message=response_message, - content=response_message.content, - original_result=result, - prompt=Prompt( - messages=messages, # Original messages - functions=call_kwargs.get("tools"), - max_tokens=max_tokens, - temperature=kwargs.get("temperature"), - top_p=kwargs.get("top_p"), - ), - ) + return OpenAIStreamResponse( + stream_function=stream_function, + check_connection=check_connection, + stream_params=stream_params, + original_result=result, + client=client, + initial_call_kwargs=call_kwargs, + tool_registry=tool_registry, + mcp_call_registry=mcp_call_registry, + max_tool_iterations=max_tool_iterations, + initial_messages_history=current_messages_history, + prompt=Prompt( + messages=kwargs.get("messages"), + functions=kwargs.get("tools"), + max_tokens=max_tokens, + temperature=kwargs.get("temperature"), + top_p=kwargs.get("top_p"), + ), + ).stream() except Exception as e: logger.exception("[OPENAI] failed to handle chat stream", exc_info=e) raise_openai_exception(e) @@ -247,8 +198,9 @@ class OpenAIStreamResponse(OpenAIResponse): tool_registry: t.Dict[str, ToolDefinition] mcp_call_registry: t.Dict[str, t.Callable] max_tool_iterations: int - initial_call_kwargs: dict # Original non-message kwargs - initial_messages_history: t.List[dict] # Original messages list *with tool prompts injected* + initial_call_kwargs: dict + initial_messages_history: t.List[dict] + tool_registry: t.Dict[str, t.Callable] # Internal state for the stream method _current_messages_history: t.List[dict] = field(init=False, default_factory=list) @@ -265,18 +217,8 @@ def process_message(self, text: str, idx: int): return self.stream_function(text, **self.stream_params) - def _handle_tool_call(self, - tool_name: str, - parameters: dict) -> str: - """Handle a tool call by executing the corresponding function from the MCP registry. - - Args: - tool_name: Name of the tool to execute - parameters: Parameters for the tool - - Returns: - String representation of the tool result - """ + def _handle_tool_call(self, tool_name: str, parameters: dict) -> str: + """Handle a tool call by executing the corresponding function from the MCP registry.""" tool_function = self.mcp_call_registry.get(tool_name) if not tool_function: logger.warning(f"Tool '{tool_name}' not found in MCP registry") @@ -291,60 +233,72 @@ def _handle_tool_call(self, logger.exception(f"Error executing MCP tool '{tool_name}'", exc_info=e) return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) - def stream(self): - """ - Processes the stream, parses for custom blocks, - executes tools, and restarts the stream if necessary. + def _process_stream_response(self, stream_iterator) -> t.Tuple[str, str, t.Optional[dict]]: + """Process a single streaming response from OpenAI. + + Returns: + Tuple of (content, stop_reason, detected_tool_call) """ + current_stream_part_content = "" + stream_stop_reason = None + detected_tool_call = None + stream_idx = 0 + + try: + logger.debug("Processing stream chunks...") + for chunk in stream_iterator: + stream_idx += 1 + if not chunk.choices: + continue + + delta = chunk.choices[0].delta + finish_reason = chunk.choices[0].finish_reason + stream_stop_reason = finish_reason + + if delta and delta.content: + text_chunk = delta.content + current_stream_part_content += text_chunk + self.process_message(text_chunk, stream_idx) + + if finish_reason: + logger.debug(f"Stream part finished with reason: {finish_reason}") + break + + # Check for tool call after accumulating content + detected_tool_call = parse_tool_call_block(current_stream_part_content) + if detected_tool_call: + logger.info(f'Found tool call in the response: {detected_tool_call}') + + except ConnectionLostError: + raise + except Exception as e: + logger.exception("Exception during stream processing", exc_info=e) + raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e + + return current_stream_part_content, stream_stop_reason, detected_tool_call + + def stream(self) -> t.Self: + """Process the stream, handle tool calls, and manage conversation history.""" iteration_count = 0 - current_stream_iterator = self.original_result # Start with the initial iterator + current_stream_iterator = self.original_result while iteration_count < self.max_tool_iterations: iteration_count += 1 - logger.info(f"--- Custom Tool Streaming Iteration: {iteration_count} ---") + logger.info(f"--- OpenAI Tool Streaming Iteration: {iteration_count} ---") - current_stream_part_content = "" - current_finish_reason = None - assistant_response_for_history = {"role": "assistant", "content": ""} # To store raw assistant text - - stream_idx = 0 try: - logger.debug("Processing stream chunks...") - for chunk in current_stream_iterator: - stream_idx += 1 - if not chunk.choices: - continue - - delta = chunk.choices[0].delta - finish_reason = chunk.choices[0].finish_reason - current_finish_reason = finish_reason # Track the latest finish reason - - # Accumulate content and stream out using process_message - if delta and delta.content: - text_chunk = delta.content - current_stream_part_content += text_chunk - self.process_message(text_chunk, stream_idx) - - # If stream part finished, break inner loop - if finish_reason: - logger.debug(f"Stream part finished with reason: {finish_reason}") - break - - # --- After processing chunks for this stream part --- - logger.debug(f"Accumulated content for parsing: {current_stream_part_content[:500]}...") - # Update the assistant message content for history - assistant_response_for_history["content"] = current_stream_part_content - # Add the raw assistant response to our internal history *before* checking for tool call - self._current_messages_history.append(assistant_response_for_history) - - # Parse the *accumulated* text for the custom tool block - parsed_tool_call = parse_tool_call_block(current_stream_part_content) - - if parsed_tool_call: - tool_name = parsed_tool_call.get("tool_name") - parameters = parsed_tool_call.get("parameters", {}) - logger.info(f"Custom tool call block parsed: {tool_name}") + current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( + current_stream_iterator + ) + # Add the raw assistant response to history + assistant_message = {"role": "assistant", "content": current_stream_part_content} + self._current_messages_history.append(assistant_message) + + if detected_tool_call: + tool_name = detected_tool_call.get("tool_name") + parameters = detected_tool_call.get("parameters", {}) + # Execute the tool and get result tool_result_str = self._handle_tool_call(tool_name, parameters) @@ -352,46 +306,40 @@ def stream(self): tool_result_message = format_tool_result_message(tool_name, tool_result_str) self._current_messages_history.append(tool_result_message) - # --- Make a *new* streaming call --- - logger.info("Restarting stream after custom tool execution.") + # Make a new streaming call with updated history + logger.info("Restarting stream after tool execution") new_call_kwargs = { **self.initial_call_kwargs, - "messages": self._current_messages_history, - "stream": True, + "messages": self._current_messages_history, + "stream": True, } - current_stream_iterator = self.client.chat.completions.create(**new_call_kwargs) continue - # --- No Tool Call Found in this stream part --- - else: - logger.info("No custom tool call block found in this stream part.") - self._total_accumulated_content = current_stream_part_content # Store final content - self.finish_reason = current_finish_reason or "stop" - break + # No tool call found - finish streaming + self._total_accumulated_content = current_stream_part_content + self.finish_reason = stream_stop_reason or "stop" + break - # --- Exception Handling for the inner loop --- except ConnectionLostError as cle: - logger.error("Connection lost during stream processing.", exc_info=cle) - self.finish_reason = "error_connection_lost" - raise cle + logger.error("Connection lost during stream processing", exc_info=cle) + self.finish_reason = "error_connection_lost" + raise cle except Exception as e: - logger.exception("Exception during custom stream chunk processing", exc_info=e) + logger.exception("Exception during stream processing", exc_info=e) self.finish_reason = "error_processing_stream" raise_openai_exception(e) - # --- End of outer while loop --- if iteration_count >= self.max_tool_iterations: - logger.warning(f"Reached maximum tool call iterations ({self.max_tool_iterations}) during custom streaming.") - self.finish_reason = "error_max_tool_iterations" - self._total_accumulated_content = current_stream_part_content + logger.warning(f"Reached maximum tool call iterations ({self.max_tool_iterations})") + self.finish_reason = "error_max_tool_iterations" - # Populate final fields of the response object + # Set final response fields self.content = self._total_accumulated_content - self.message = Message( # Use your Message class structure - content=self.content, - role="assistant" - ) + self.message = Message( + content=self.content, + role="assistant" + ) - logger.debug(f"Custom stream processing complete. Final finish reason: {self.finish_reason}") + logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") return self \ No newline at end of file diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 653f0c7..9f27bc4 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -4,6 +4,7 @@ from decimal import Decimal import requests import time +from lamoom.ai_models.tools.errors import ToolCallError from lamoom.settings import LAMOOM_API_URI from lamoom import Secrets, settings from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER @@ -299,13 +300,15 @@ def call( ) return result except RetryableCustomError as e: - logger.error( + logger.exception( f"Attempt failed: {prompt_attempts.current_attempt} with retryable error: {e}" ) + break except Exception as e: - logger.error( + logger.exception( f"Attempt failed: {prompt_attempts.current_attempt} with non-retryable error: {e}" ) + break logger.exception( "Prompt call failed, no attempts worked" diff --git a/lamoom/responses.py b/lamoom/responses.py index 26866e3..5f90591 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -8,18 +8,12 @@ @dataclass -class Prompt(BasePrompt): - id: str = None - max_tokens: int = None - min_sample_tokens: int = settings.DEFAULT_SAMPLE_MIN_BUDGET - reserved_tokens_budget_for_sampling: int = None - version: str = None - # Add tool registry - tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) - - def add_tool(self, tool: ToolDefinition): - """Add a tool to this prompt's registry""" - self.tool_registry[tool.name] = tool +class Prompt: + messages: dict = None + functions: dict = None + max_tokens: int = 0 + temperature: Decimal = Decimal(0.0) + top_p: Decimal = Decimal(0.0) @dataclass @@ -43,7 +37,7 @@ class AIResponse: @property def response(self) -> str: - return self._response + return self._response or self.content or '{}' def get_message_str(self) -> str: - return json.loads(self.response) + return self.response diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 39bb835..0a11b56 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -3,6 +3,7 @@ import time from pytest import fixture from lamoom import Lamoom, Prompt +from lamoom.ai_models.tools.web_tool import WEB_SEARCH_TOOL logger = logging.getLogger(__name__) @@ -27,7 +28,7 @@ def test_web_call(client): } # initial version of the prompt - prompt_id = f'test-{time.time()}' + prompt_id = 'test-web-search' client.service.clear_cache() prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') From 64dcc23b1acb0e451045668c1cc1f600df469d7b Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Sun, 4 May 2025 17:13:51 -0700 Subject: [PATCH 08/34] added files --- lamoom/ai_models/tools/base_tool.py | 198 ++++++++++++++++++++++++++++ lamoom/ai_models/tools/errors.py | 2 + lamoom/ai_models/tools/web_tool.py | 115 ++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 lamoom/ai_models/tools/base_tool.py create mode 100644 lamoom/ai_models/tools/errors.py create mode 100644 lamoom/ai_models/tools/web_tool.py diff --git a/lamoom/ai_models/tools/base_tool.py b/lamoom/ai_models/tools/base_tool.py new file mode 100644 index 0000000..1e07af0 --- /dev/null +++ b/lamoom/ai_models/tools/base_tool.py @@ -0,0 +1,198 @@ +import logging +import typing as t +from dataclasses import dataclass +import json +import re + + +logger = logging.getLogger(__name__) + +# --- Constants for Prompting --- +TOOL_CALL_START_TAG = "" +TOOL_CALL_END_TAG = "" + + +def get_tool_system_prommpt(tool_descriptions: str): + return f"""You have next skills: +``` +{tool_descriptions} +``` + +If you wish to make a call you need to make a call: +``` +""" + TOOL_CALL_START_TAG + """ +{ +"tool_name": "...", +"parameters": { + // parameters of the tool +} +} +""" + TOOL_CALL_END_TAG + """ +""" + + +@dataclass +class ToolCallResult: + content: str + has_tool_call: bool + tool_name: t.Optional[str] = None + parameters: t.Optional[dict] = None + execution_result: str = None + + +@dataclass +class ToolParameter: + name: str + type: str + description: str + required: bool = True + + +@dataclass +class ToolDefinition: + name: str + description: str + parameters: t.List[ToolParameter] + execution_function: t.Callable + + +def format_tool_description(tool: ToolDefinition) -> str: + """Formats a single tool's description for the prompt.""" + param_desc = ",\n".join([f'"{p.name}": ... \\ {p.type} - ({p.description})' for p in tool.parameters]) + return f"//{tool.description}\n- {tool.name}({{{param_desc}}})" + + +def inject_tool_prompts( + messages: t.List[dict], + available_tools: t.List[ToolDefinition] + ) -> t.List[dict]: + """Injects tool descriptions and usage instructions into the system prompt.""" + if not available_tools: + return messages + + tool_descriptions = "\n".join([format_tool_description(tool) for tool in available_tools]) + tool_system_prompt = get_tool_system_prommpt(tool_descriptions) + # Find system prompt or prepend to user prompt + modified_messages = list(messages) # Create a copy + found_system = False + for i, msg in enumerate(modified_messages): + if msg.get("role") == "system": + # Append to existing system prompt + modified_messages[i]["content"] = f"{msg.get('content', '')}\n\n{tool_system_prompt}" + found_system = True + break + + if not found_system: + # Prepend a new system message + modified_messages.insert(0, {"role": "system", "content": tool_system_prompt}) + + logger.debug(f"Injected tool system prompt:\n{tool_system_prompt}") + return modified_messages + + +def parse_tool_call_block(text_response: str) -> t.Optional[ToolCallResult]: + """ + Parses the block from the model's text response using regex. + Returns a ToolCallResult object if a valid tool call is found, None otherwise. + """ + if not text_response: + return None + + # Regex to find the block, allowing for whitespace variations + # DOTALL allows '.' to match newlines within the JSON block + match = re.search( + rf"{re.escape(TOOL_CALL_START_TAG)}(.*?){re.escape(TOOL_CALL_END_TAG)}", + text_response, + re.DOTALL | re.IGNORECASE + ) + + if not match: + return None + json_content = match.group(1).strip() + logger.debug(f"Found potential tool call JSON block: {json_content}") + + try: + parsed_data = json.loads(json_content) + # Basic validation + if "tool_name" in parsed_data and ( + "parameters" in parsed_data and isinstance(parsed_data["parameters"], dict) or "parameters" not in parsed_data + ): + logger.info(f"Successfully parsed tool call: {parsed_data['tool_name']}") + return ToolCallResult( + content=text_response, + has_tool_call=True, + tool_name=parsed_data.get("tool_name"), + parameters=parsed_data.get("parameters", {}), + execution_result="" + ) + else: + logger.warning(f"Parsed JSON block lacks required 'tool_name' or 'parameters': {json_content}") + return None + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON from tool call block: {json_content}", exc_info=e) + return None + + +def call_function(tool_name: str, parameters: dict, tool_registry={}) -> str: + """Handle a tool call by executing the corresponding function from the MCP registry. + + Args: + tool_name: Name of the tool to execute + parameters: Parameters for the tool + + Returns: + String representation of the tool result + """ + tool_function = tool_registry.get(tool_name) + if not tool_function: + logger.warning(f"Tool '{tool_name}' not found in MCP registry") + return json.dumps({"error": f"Tool '{tool_name}' is not available."}) + tool_execution_function = tool_function.execution_function + try: + logger.info(f"Executing MCP tool '{tool_name}' with parameters: {parameters}") + result = tool_execution_function(**parameters) + logger.info(f"MCP tool '{tool_name}' executed successfully: {result}") + return json.dumps({"result": result}) + except Exception as e: + logger.exception(f"Error executing MCP tool '{tool_name}'", exc_info=e) + return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) + + +def handle_tool_call(current_stream_part_content, tool_registry) -> ToolCallResult: + """Handle a tool call by executing the corresponding function from the MCP registry. + + Args: + current_stream_part_content: The current content of the stream + tool_registry: Registry of available tools + + Returns: + ToolCallResult object containing the result of the tool call + """ + parsed_tool_call = parse_tool_call_block(current_stream_part_content) + + if not parsed_tool_call: + return None + tool_name = parsed_tool_call.tool_name + parameters = parsed_tool_call.parameters + logger.info(f"Custom tool call block parsed: {tool_name}") + + # Execute the tool and get result + tool_result_str = call_function(tool_name, parameters, tool_registry=tool_registry) + + return ToolCallResult( + content=current_stream_part_content, + has_tool_call=True, + tool_name=tool_name, + parameters=parameters, + execution_result=tool_result_str, + ) + + +def format_tool_result_message(tool_name, tool_result_str): + return f''' +{{ +"tool_name": "{tool_name}", +"parameters": "{tool_result_str}" +}} + +''' \ No newline at end of file diff --git a/lamoom/ai_models/tools/errors.py b/lamoom/ai_models/tools/errors.py new file mode 100644 index 0000000..12d5c4a --- /dev/null +++ b/lamoom/ai_models/tools/errors.py @@ -0,0 +1,2 @@ +class ToolCallError(Exception): + pass diff --git a/lamoom/ai_models/tools/web_tool.py b/lamoom/ai_models/tools/web_tool.py new file mode 100644 index 0000000..861bf7e --- /dev/null +++ b/lamoom/ai_models/tools/web_tool.py @@ -0,0 +1,115 @@ + +import os +import requests +from bs4 import BeautifulSoup +from dataclasses import dataclass +import typing as t +from dotenv import load_dotenv +import logging + +from lamoom.ai_models.tools.base_tool import ToolDefinition, ToolParameter + +load_dotenv() + +logger = logging.getLogger(__name__) + +API_KEY = os.getenv("GOOGLE_API_KEY") +SEARCH_ID = os.getenv("SEARCH_ENGINE_ID") + +@dataclass +class WebSearchResult: + url: str + title: str + snippet: str + content: str + +class WebCall: + @staticmethod + def scrape_webpage(url: str) -> str: + """ + Scrapes the content of a webpage and returns the text. + """ + if not url.startswith(("https://", "http://")): + url = "https://" + url + response = requests.get(url) + + if response.status_code == 200: + soup = BeautifulSoup(response.content, "html.parser") + text = soup.get_text() + clean_text = text.splitlines() + clean_text = [element.strip() + for element in clean_text if element.strip()] + clean_text = '\n'.join(clean_text) + return clean_text + else: + return "Failed to retrieve the website content." + + @staticmethod + def perform_web_search(query: str) -> t.List[WebSearchResult]: + """ + Performs a web search and returns a list of search results. + """ + url = "https://www.googleapis.com/customsearch/v1" + params = { + 'q': query, + 'key': API_KEY, + 'cx': SEARCH_ID, + 'num': 3 + } + + response = requests.get(url, params=params) + results = response.json() + + search_results = [] + + if 'items' in results: + for result in results['items']: + content = WebCall.scrape_webpage(result['link']) + search_results.append(WebSearchResult( + url=result['link'], + title=result['title'], + snippet=result['snippet'], + content=content + )) + logger.debug(f"Retrieved search_results {search_results}") + return search_results + + @staticmethod + def format_search_results(results: t.List[WebSearchResult]) -> str: + """ + Formats search results into a readable string. + """ + formatted = "" + for i, result in enumerate(results, 1): + formatted += f"\n" + formatted += f"Title: {result.title}\n" + formatted += f"URL: {result.url}\n" + formatted += f"Snippet: {result.snippet}\n" + formatted += f"Content: {result.content[:500]}...\n" + formatted += '' + logger.debug(f"Adding into LLM context\n:{formatted}") + return formatted + + @staticmethod + def execute(query: str) -> str: + return WebCall.format_search_results( + WebCall.perform_web_search(query) + ) + + +def perform_web_search(query: str) -> str: + """ + Performs a web search and returns formatted results. + """ + results = WebCall.execute(query) + return results + + +WEB_SEARCH_TOOL = ToolDefinition( + name="web_call", + description="Performs a web search using a search engine to find up-to-date information or details not present in the internal knowledge.", + parameters=[ + ToolParameter(name="query", type="string", description="The search query to use.", required=True) + ], + execution_function=perform_web_search +) From f978306370b0d4aceb0c4e6bf26416f0f46b4e4d Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 7 May 2025 17:19:14 -0700 Subject: [PATCH 09/34] updated --- lamoom/ai_models/ai_model.py | 177 ++- lamoom/ai_models/claude/claude_model.py | 17 +- lamoom/ai_models/openai/openai_models.py | 291 ++--- lamoom/ai_models/openai/responses.py | 21 +- poetry.lock | 1309 ++-------------------- pyproject.toml | 2 +- tests/prompts/test_pricing.py | 2 +- 7 files changed, 404 insertions(+), 1415 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 4804657..84b96e8 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,11 +1,15 @@ import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from _decimal import Decimal +from lamoom.ai_models.tools.base_tool import ToolDefinition from lamoom.responses import AIResponse +from logging import getLogger + +logger = getLogger(__name__) class AI_MODELS_PROVIDER(Enum): OPENAI = "openai" @@ -37,3 +41,174 @@ def call(self, *args, **kwargs) -> AIResponse: def get_metrics_data(self): return {} + + + + +@dataclass(kw_only=True) +class StreamResponse(AIResponse): + stream_function: t.Callable + check_connection: t.Callable + stream_params: dict + + client: AIModel + tool_registry: t.Dict[str, ToolDefinition] + max_tool_iterations: int + initial_call_kwargs: dict + initial_messages_history: t.List[dict] + first_response_to_user_ts: t.Optional[int] = None + exception: t.Optional[Exception] = None + + # Internal state for the stream method + _current_messages_history: t.List[dict] = field(init=False, default_factory=list) + _total_accumulated_content: str = field(init=False, default="") + + def __post_init__(self): + self._current_messages_history = list(self.initial_messages_history) + + def process_message(self, text: str, idx: int): + if idx % 5 == 0: + if not self.check_connection(**self.stream_params): + raise ConnectionLostError("Connection was lost!") + if not text: + return + self.stream_function(text, **self.stream_params) + + def _handle_tool_call(self, tool_name: str, parameters: dict) -> str: + """Handle a tool call by executing the corresponding function from the MCP registry.""" + tool_function = self.mcp_call_registry.get(tool_name) + if not tool_function: + logger.warning(f"Tool '{tool_name}' not found in MCP registry") + return json.dumps({"error": f"Tool '{tool_name}' is not available."}) + + try: + logger.info(f"Executing MCP tool '{tool_name}' with parameters: {parameters}") + result = tool_function(**parameters) + logger.info(f"MCP tool '{tool_name}' executed successfully") + return json.dumps({"result": result}) + except Exception as e: + logger.exception(f"Error executing MCP tool '{tool_name}'", exc_info=e) + return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) + + def _process_stream_response(self, stream_iterator) -> t.Tuple[str, str, t.Optional[dict]]: + """Process a single streaming response from OpenAI. + + Returns: + Tuple of (content, stop_reason, detected_tool_call) + """ + current_stream_part_content = "" + stream_stop_reason = None + detected_tool_call = None + stream_idx = 0 + + try: + logger.debug("Processing stream chunks...") + content = "" + for i, data in enumerate(self.original_result): + if not data.choices: + continue + msg = data.choices[0] + delta = msg.delta + if delta: + content += delta.content or "" + self.process_message(delta.content, i) + + if '' in delta: + detected_tool_call = parse_tool_call_block(delta.content) + if detected_tool_call: + logger.info(f"Detected tool call: {detected_tool_call}") + break + + if 'content' in delta: + if not first_response_to_user_ts: + first_response_to_user_ts = curr_timestamp_in_ms() + chat_stream_response.first_response_to_user_ts = ( + first_response_to_user_ts + ) + generated_text = delta.content + chat_stream_response.content += generated_text or '' + process_messages(generated_text, i) + + if message.choices and 'finish_reason' in message.choices[0]: + finish_reason = message.choices[0].finish_reason + chat_stream_response.message = { + 'role': 'assistant', + 'content': chat_stream_response.content, + } + chat_stream_response.finish_reason = finish_reason + return chat_stream_response + + except ConnectionLostError: + raise + except Exception as e: + logger.exception("Exception during stream processing", exc_info=e) + raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e + + return current_stream_part_content, stream_stop_reason, detected_tool_call + + def stream(self) -> t.Self: + """Process the stream, handle tool calls, and manage conversation history.""" + iteration_count = 0 + current_stream_iterator = self.original_result + + while iteration_count < self.max_tool_iterations: + iteration_count += 1 + logger.info(f"--- OpenAI Tool Streaming Iteration: {iteration_count} ---") + + try: + current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( + current_stream_iterator + ) + + # Add the raw assistant response to history + assistant_message = {"role": "assistant", "content": current_stream_part_content} + self._current_messages_history.append(assistant_message) + + if detected_tool_call: + tool_name = detected_tool_call.get("tool_name") + parameters = detected_tool_call.get("parameters", {}) + + # Execute the tool and get result + tool_result_str = self._handle_tool_call(tool_name, parameters) + + # Add tool result to history + tool_result_message = format_tool_result_message(tool_name, tool_result_str) + self._current_messages_history.append(tool_result_message) + + # Make a new streaming call with updated history + logger.info("Restarting stream after tool execution") + new_call_kwargs = { + **self.initial_call_kwargs, + "messages": self._current_messages_history, + "stream": True, + } + current_stream_iterator = self.client.chat.completions.create(**new_call_kwargs) + continue + + # No tool call found - finish streaming + self._total_accumulated_content = current_stream_part_content + self.finish_reason = stream_stop_reason or "stop" + break + + except ConnectionLostError as cle: + logger.error("Connection lost during stream processing", exc_info=cle) + self.finish_reason = "error_connection_lost" + raise cle + except Exception as e: + logger.exception("Exception during stream processing", exc_info=e) + self.finish_reason = "error_processing_stream" + raise_openai_exception(e) + + if iteration_count >= self.max_tool_iterations: + logger.warning(f"Reached maximum tool call iterations ({self.max_tool_iterations})") + self.finish_reason = "error_max_tool_iterations" + + # Set final response fields + self.content = self._total_accumulated_content + self.message = Message( + content=self.content, + role="assistant" + ) + + logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") + return self \ No newline at end of file diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index f5791ed..d47c92b 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -66,7 +66,6 @@ def _process_stream_response(self, client: anthropic.Anthropic, current_messages_history: t.List[dict], max_tokens: int, - system_prompt: t.Optional[str], stream_function: t.Callable, check_connection: t.Callable, stream_params: dict, @@ -81,7 +80,7 @@ def _process_stream_response(self, stream_function: Function to call for each text chunk check_connection: Function to check connection status stream_params: Parameters for stream function - mcp_call_registry: Registry of available MCP functions + tool_registry: Registry of available MCP functions Returns: Tuple of (content, stop_reason, has_tool_call) @@ -95,9 +94,13 @@ def _process_stream_response(self, "max_tokens": max_tokens, "messages": current_messages_history, } + system_prompt = [] + for i, msg in enumerate(current_messages_history): + if msg.get('role') == "system": + system_prompt += [current_messages_history.pop(i - len(system_prompt)).get('content')] if system_prompt: - call_kwargs["system"] = system_prompt + call_kwargs["system"] = '\n'.join(system_prompt) stream_idx = 0 try: @@ -130,8 +133,6 @@ def _process_stream_response(self, current_messages_history.append( {"role": "assistant", "content": current_stream_part_content + detected_tool_call.execution_result} ) - - except ConnectionLostError: raise except anthropic.APIError as e: @@ -163,10 +164,6 @@ def call(self, # Inject Tool Prompts into initial messages current_messages_history = inject_tool_prompts(messages, list(tool_registry.values())) - system_prompt = None - if current_messages_history and current_messages_history[0].get('role') == "system": - system_prompt = current_messages_history[0].get('content') - current_messages_history = current_messages_history[1:] logger.debug( f"Calling {current_messages_history} with max_tokens {max_tokens} and kwargs {kwargs}" @@ -185,7 +182,7 @@ def call(self, logger.info(f"--- Custom Claude Tool Streaming Iteration: {iteration_count} ---") current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( - client, current_messages_history, max_tokens, system_prompt, + client, current_messages_history, max_tokens, stream_function, check_connection, stream_params, tool_registry ) diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index fbc49c7..4cad216 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -8,9 +8,9 @@ from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER, AIModel from lamoom.ai_models.constants import C_128K, C_16K, C_32K, C_4K -from lamoom.ai_models.openai.responses import OpenAIResponse +from lamoom.ai_models.openai.responses import OpenAIResponse, StreamingResponse from lamoom.ai_models.utils import get_common_args -from lamoom.exceptions import ConnectionLostError +from lamoom.exceptions import ConnectionLostError, RetryableCustomError from lamoom.ai_models.tools.base_tool import ToolDefinition, inject_tool_prompts, parse_tool_call_block import json @@ -129,9 +129,7 @@ def call_chat_completion( self, messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], - functions: t.List[t.Dict[str, str]] = [], tool_registry: t.Dict[str, ToolDefinition] = {}, - mcp_call_registry: t.Dict[str, t.Callable] = None, max_tool_iterations: int = 5, # Safety limit for sequential calls stream_function: t.Callable = None, check_connection: t.Callable = None, @@ -139,207 +137,124 @@ def call_chat_completion( client_secrets: dict = {}, **kwargs, ) -> OpenAIResponse: - client = self.get_client(client_secrets) tool_definitions = list(tool_registry.values()) - - # Inject Tool Prompts into initial messages - current_messages_history = inject_tool_prompts(messages, tool_definitions) - iteration_count = 0 - while iteration_count < max_tool_iterations: - iteration_count += 1 - - logger.info(f"--- Generic Tool Call Iteration: {iteration_count} ---") - - call_kwargs = { - **{ - "messages": current_messages_history, - }, - **self.get_params(), - **kwargs, - } - + # Prepare streaming response + stream_response = StreamingResponse( + tool_registry=tool_registry, + messages=messages + ) + + # Inject tool prompts into first message + current_messages = inject_tool_prompts(messages, tool_definitions) + + attempts = max_tool_iterations + while attempts > 0: try: - result = client.chat.completions.create( - **call_kwargs, - ) - return OpenAIStreamResponse( + stream_response = self._streaming( + client=client, + messages=current_messages, + max_tokens=max_tokens, stream_function=stream_function, check_connection=check_connection, stream_params=stream_params, - original_result=result, - client=client, - initial_call_kwargs=call_kwargs, - tool_registry=tool_registry, - mcp_call_registry=mcp_call_registry, - max_tool_iterations=max_tool_iterations, - initial_messages_history=current_messages_history, - prompt=Prompt( - messages=kwargs.get("messages"), - functions=kwargs.get("tools"), - max_tokens=max_tokens, - temperature=kwargs.get("temperature"), - top_p=kwargs.get("top_p"), - ), - ).stream() - except Exception as e: - logger.exception("[OPENAI] failed to handle chat stream", exc_info=e) - raise_openai_exception(e) - - -@dataclass(kw_only=True) -class OpenAIStreamResponse(OpenAIResponse): - stream_function: t.Callable - check_connection: t.Callable - stream_params: dict - - client: OpenAI - tool_registry: t.Dict[str, ToolDefinition] - mcp_call_registry: t.Dict[str, t.Callable] - max_tool_iterations: int - initial_call_kwargs: dict - initial_messages_history: t.List[dict] - tool_registry: t.Dict[str, t.Callable] - - # Internal state for the stream method - _current_messages_history: t.List[dict] = field(init=False, default_factory=list) - _total_accumulated_content: str = field(init=False, default="") - - def __post_init__(self): - self._current_messages_history = list(self.initial_messages_history) - - def process_message(self, text: str, idx: int): - if idx % 5 == 0: - if not self.check_connection(**self.stream_params): - raise ConnectionLostError("Connection was lost!") - if not text: - return - self.stream_function(text, **self.stream_params) + stream_response=stream_response, + **kwargs + ) + + if stream_response.is_detected_tool_call: + parsed_tool_call = parse_tool_call_block(stream_response.detected_tool_call) + if not parsed_tool_call: + continue + + # Execute tool call + tool_result = self.handle_tool_call(parsed_tool_call, tool_registry) + + # Add messages to history + stream_response.add_message("assistant", stream_response.content) + stream_response.add_tool_result(parsed_tool_call, tool_result) + + # Update messages for next iteration + current_messages = stream_response.messages + attempts -= 1 + continue + + break + + except RetryableCustomError: + attempts -= 1 + continue + + return stream_response - def _handle_tool_call(self, tool_name: str, parameters: dict) -> str: - """Handle a tool call by executing the corresponding function from the MCP registry.""" - tool_function = self.mcp_call_registry.get(tool_name) + def handle_tool_call(self, tool_name: str, parameters: dict, tool_registry: t.Dict[str, ToolDefinition]) -> str: + """Handle a tool call by executing the corresponding function from the registry.""" + tool_function = tool_registry.get(tool_name) if not tool_function: - logger.warning(f"Tool '{tool_name}' not found in MCP registry") + logger.warning(f"Tool '{tool_name}' not found in registry") return json.dumps({"error": f"Tool '{tool_name}' is not available."}) - try: - logger.info(f"Executing MCP tool '{tool_name}' with parameters: {parameters}") - result = tool_function(**parameters) - logger.info(f"MCP tool '{tool_name}' executed successfully") + logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + result = tool_function.execution_function(**parameters) + logger.info(f"Tool '{tool_name}' executed successfully") return json.dumps({"result": result}) except Exception as e: - logger.exception(f"Error executing MCP tool '{tool_name}'", exc_info=e) + logger.exception(f"Error executing tool '{tool_name}'", exc_info=e) return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) - def _process_stream_response(self, stream_iterator) -> t.Tuple[str, str, t.Optional[dict]]: - """Process a single streaming response from OpenAI. - - Returns: - Tuple of (content, stop_reason, detected_tool_call) - """ - current_stream_part_content = "" - stream_stop_reason = None - detected_tool_call = None - stream_idx = 0 - + def streaming( + self, + client: OpenAI, + messages: t.List[dict], + max_tokens: int, + stream_function: t.Callable, + check_connection: t.Callable, + stream_params: dict, + stream_response: StreamingResponse, + **kwargs + ) -> StreamingResponse: + """Process streaming response from OpenAI.""" + tool_call_started = False + content = "" try: - logger.debug("Processing stream chunks...") - for chunk in stream_iterator: - stream_idx += 1 - if not chunk.choices: + call_kwargs = { + "messages": messages, + "max_tokens": max_tokens, + "stream": True, + **self.get_params(), + **kwargs + } + + for part in client.chat.completions.create(**call_kwargs): + if not part.choices: continue - - delta = chunk.choices[0].delta - finish_reason = chunk.choices[0].finish_reason - stream_stop_reason = finish_reason - - if delta and delta.content: - text_chunk = delta.content - current_stream_part_content += text_chunk - self.process_message(text_chunk, stream_idx) - - if finish_reason: - logger.debug(f"Stream part finished with reason: {finish_reason}") - break - - # Check for tool call after accumulating content - detected_tool_call = parse_tool_call_block(current_stream_part_content) - if detected_tool_call: - logger.info(f'Found tool call in the response: {detected_tool_call}') - - except ConnectionLostError: - raise - except Exception as e: - logger.exception("Exception during stream processing", exc_info=e) - raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e - - return current_stream_part_content, stream_stop_reason, detected_tool_call - - def stream(self) -> t.Self: - """Process the stream, handle tool calls, and manage conversation history.""" - iteration_count = 0 - current_stream_iterator = self.original_result - - while iteration_count < self.max_tool_iterations: - iteration_count += 1 - logger.info(f"--- OpenAI Tool Streaming Iteration: {iteration_count} ---") - - try: - current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( - current_stream_iterator - ) - - # Add the raw assistant response to history - assistant_message = {"role": "assistant", "content": current_stream_part_content} - self._current_messages_history.append(assistant_message) - - if detected_tool_call: - tool_name = detected_tool_call.get("tool_name") - parameters = detected_tool_call.get("parameters", {}) - # Execute the tool and get result - tool_result_str = self._handle_tool_call(tool_name, parameters) + delta = part.choices[0].delta + if not delta: + continue - # Add tool result to history - tool_result_message = format_tool_result_message(tool_name, tool_result_str) - self._current_messages_history.append(tool_result_message) - - # Make a new streaming call with updated history - logger.info("Restarting stream after tool execution") - new_call_kwargs = { - **self.initial_call_kwargs, - "messages": self._current_messages_history, - "stream": True, - } - current_stream_iterator = self.client.chat.completions.create(**new_call_kwargs) + if delta.content: + content += delta.content + if stream_function: + stream_function(delta.content, **stream_params) + + if check_connection and not check_connection(**stream_params): + raise ConnectionLostError("Connection was lost!") + + # Check for tool call markers + if "" in content: + if not tool_call_started: + tool_call_started = True continue - - # No tool call found - finish streaming - self._total_accumulated_content = current_stream_part_content - self.finish_reason = stream_stop_reason or "stop" - break - - except ConnectionLostError as cle: - logger.error("Connection lost during stream processing", exc_info=cle) - self.finish_reason = "error_connection_lost" - raise cle - except Exception as e: - logger.exception("Exception during stream processing", exc_info=e) - self.finish_reason = "error_processing_stream" - raise_openai_exception(e) - - if iteration_count >= self.max_tool_iterations: - logger.warning(f"Reached maximum tool call iterations ({self.max_tool_iterations})") - self.finish_reason = "error_max_tool_iterations" - - # Set final response fields - self.content = self._total_accumulated_content - self.message = Message( - content=self.content, - role="assistant" - ) - - logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") - return self \ No newline at end of file + + if tool_call_started and "" in content: + stream_response.is_detected_tool_call = True + stream_response.detected_tool_call = content + break + stream_response.content = content + return stream_response + except Exception as e: + stream_response.content = content + logger.exception("Exception during stream processing", exc_info=e) + raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e \ No newline at end of file diff --git a/lamoom/ai_models/openai/responses.py b/lamoom/ai_models/openai/responses.py index fc66708..760f4cc 100644 --- a/lamoom/ai_models/openai/responses.py +++ b/lamoom/ai_models/openai/responses.py @@ -1,12 +1,13 @@ import json import logging import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, field from openai.types.chat import ChatCompletionMessage as Message from openai.types.chat import ChatCompletionMessageToolCall as ToolCall from lamoom.responses import AIResponse +from lamoom.ai_models.tools.base_tool import ToolDefinition FINISH_REASON_LENGTH = "length" FINISH_REASON_ERROR = "error" @@ -16,6 +17,24 @@ logger = logging.getLogger(__name__) +@dataclass(kw_only=True) +class StreamingResponse(AIResponse): + is_detected_tool_call: bool = False + detected_tool_call: t.Optional[dict] = None + tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) + messages: t.List[dict] = field(default_factory=list) + + def add_message(self, role: str, content: str): + self.messages.append({"role": role, "content": content}) + + def add_tool_result(self, tool_name: str, result: str): + self.messages.append({ + "role": "tool", + "name": tool_name, + "content": result + }) + + @dataclass(kw_only=True) class OpenAIResponse(AIResponse): message: Message = None diff --git a/poetry.lock b/poetry.lock index 343947f..4854498 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "0.31.2" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "anthropic-0.31.2-py3-none-any.whl", hash = "sha256:28d176b98c72615bfae30f0a9eee6297cc33bf52535d38156fc2805556e2f09b"}, {file = "anthropic-0.31.2.tar.gz", hash = "sha256:0134b73df8d1f142fc68675fbadb75e920054e9e3437b99df63f10f0fc6ac26f"}, @@ -42,6 +44,7 @@ version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, @@ -55,7 +58,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -64,6 +67,8 @@ version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "platform_system == \"Darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -75,6 +80,7 @@ version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -84,27 +90,13 @@ files = [ astroid = ["astroid (>=2,<4)"] test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] -[[package]] -name = "autopep8" -version = "2.3.2" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" -optional = false -python-versions = ">=3.9" -files = [ - {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, - {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, -] - -[package.dependencies] -pycodestyle = ">=2.12.0" -tomli = {version = "*", markers = "python_version < \"3.11\""} - [[package]] name = "beautifulsoup4" version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -127,6 +119,7 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -167,58 +160,13 @@ d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] -[[package]] -name = "build" -version = "1.2.2.post1" -description = "A simple, correct Python build frontend" -optional = false -python-versions = ">=3.8" -files = [ - {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, - {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "os_name == \"nt\""} -importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} -packaging = ">=19.1" -pyproject_hooks = "*" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] -typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] -uv = ["uv (>=0.1.18)"] -virtualenv = ["virtualenv (>=20.0.35)"] - -[[package]] -name = "cachecontrol" -version = "0.14.2" -description = "httplib2 caching for requests" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, - {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, -] - -[package.dependencies] -filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} -msgpack = ">=0.5.2,<2.0.0" -requests = ">=2.16.0" - -[package.extras] -dev = ["CacheControl[filecache,redis]", "build", "cherrypy", "codespell[tomli]", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] -filecache = ["filelock (>=3.8.0)"] -redis = ["redis (>=2.10.5)"] - [[package]] name = "certifi" version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -230,6 +178,8 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -303,23 +253,13 @@ files = [ [package.dependencies] pycparser = "*" -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - [[package]] name = "charset-normalizer" version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -415,27 +355,13 @@ files = [ {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] -[[package]] -name = "cleo" -version = "2.1.0" -description = "Cleo allows you to create beautiful and testable command-line interfaces." -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, - {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, -] - -[package.dependencies] -crashtest = ">=0.4.1,<0.5.0" -rapidfuzz = ">=3.0.0,<4.0.0" - [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -450,10 +376,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "comm" @@ -461,6 +389,7 @@ version = "0.2.2" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, @@ -472,150 +401,13 @@ traitlets = ">=4" [package.extras] test = ["pytest"] -[[package]] -name = "coverage" -version = "7.8.0" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, - {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, - {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, - {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, - {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, - {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, - {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, - {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, - {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, - {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, - {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, - {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, - {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, - {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, - {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, - {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, - {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "crashtest" -version = "0.4.1" -description = "Manage Python errors with ease" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, - {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, -] - -[[package]] -name = "cryptography" -version = "43.0.3" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, - {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, - {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, - {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, - {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, - {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, - {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - [[package]] name = "debugpy" version = "1.8.14" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "debugpy-1.8.14-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:93fee753097e85623cab1c0e6a68c76308cd9f13ffdf44127e6fab4fbf024339"}, {file = "debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d937d93ae4fa51cdc94d3e865f535f185d5f9748efb41d0d49e33bf3365bd79"}, @@ -651,137 +443,32 @@ version = "5.2.1" description = "Decorators for Humans" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, ] -[[package]] -name = "distlib" -version = "0.3.9" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, -] - [[package]] name = "distro" version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -[[package]] -name = "docutils" -version = "0.21.2" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, -] - -[[package]] -name = "dulwich" -version = "0.21.7" -description = "Python Git Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, - {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, - {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, - {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, - {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, - {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, - {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, - {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, - {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, - {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, - {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, - {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, - {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, - {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, - {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, - {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, - {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, - {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, - {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, - {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, - {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, - {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, - {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, - {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, - {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, - {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, - {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, - {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, - {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, - {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, - {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, - {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, - {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, - {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, - {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, - {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, - {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, - {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, - {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, - {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, - {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, - {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, - {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, - {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, - {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, - {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, - {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, - {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, - {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, - {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, - {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, - {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, - {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, - {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, - {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, - {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, - {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, - {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, - {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, - {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, - {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, - {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, - {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, - {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, - {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, - {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, - {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, - {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, - {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, -] - -[package.dependencies] -urllib3 = ">=1.25" - -[package.extras] -fastimport = ["fastimport"] -https = ["urllib3 (>=1.24.1)"] -paramiko = ["paramiko"] -pgp = ["gpg"] - [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -796,27 +483,14 @@ version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - -[[package]] -name = "fastjsonschema" -version = "2.21.1" -description = "Fastest Python implementation of JSON schema" -optional = false -python-versions = "*" -files = [ - {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, - {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, -] - -[package.extras] -devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] name = "filelock" @@ -824,6 +498,7 @@ version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -832,23 +507,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] - -[[package]] -name = "flake8" -version = "7.2.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.9" -files = [ - {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, - {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.13.0,<2.14.0" -pyflakes = ">=3.3.0,<3.4.0" +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "fsspec" @@ -856,6 +515,7 @@ version = "2025.3.2" description = "File-system specification" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711"}, {file = "fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6"}, @@ -895,6 +555,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -906,6 +567,7 @@ version = "1.0.8" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, @@ -927,6 +589,7 @@ version = "0.27.2" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -940,7 +603,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -952,6 +615,7 @@ version = "0.30.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28"}, {file = "huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466"}, @@ -981,26 +645,13 @@ testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gr torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] -[[package]] -name = "identify" -version = "2.6.10" -description = "File identification library for Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25"}, - {file = "identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8"}, -] - -[package.extras] -license = ["ukkonen"] - [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1015,6 +666,8 @@ version = "8.6.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, @@ -1024,42 +677,21 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] -[[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.8" -files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, -] - -[[package]] -name = "installer" -version = "0.7.0" -description = "A library for installing Python wheels." -optional = false -python-versions = ">=3.7" -files = [ - {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, - {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, -] - [[package]] name = "ipykernel" version = "6.29.5" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, @@ -1093,6 +725,7 @@ version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, @@ -1124,44 +757,13 @@ qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jaraco-classes" -version = "3.4.0" -description = "Utility functions for Python class constructs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, - {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, -] - -[package.dependencies] -more-itertools = "*" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [[package]] name = "jedi" version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -1175,27 +777,13 @@ docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alab qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] -[[package]] -name = "jeepney" -version = "0.9.0" -description = "Low-level, pure Python DBus protocol wrapper." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, - {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, -] - -[package.extras] -test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["trio"] - [[package]] name = "jiter" version = "0.9.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, @@ -1281,6 +869,7 @@ version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -1296,7 +885,7 @@ traitlets = ">=5.3" [package.extras] docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] -test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] [[package]] name = "jupyter-core" @@ -1304,6 +893,7 @@ version = "5.7.2" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, @@ -1318,59 +908,13 @@ traitlets = ">=5.3" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] -[[package]] -name = "keyring" -version = "24.3.1" -description = "Store and access your passwords safely." -optional = false -python-versions = ">=3.8" -files = [ - {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, - {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} -"jaraco.classes" = "*" -jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} -pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} -SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} - -[package.extras] -completion = ["shtab (>=1.1.0)"] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - [[package]] name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -1379,118 +923,13 @@ files = [ [package.dependencies] traitlets = "*" -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "more-itertools" -version = "10.6.0" -description = "More routines for operating on iterables, beyond itertools" -optional = false -python-versions = ">=3.9" -files = [ - {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, - {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, -] - -[[package]] -name = "msgpack" -version = "1.1.0" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, - {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, - {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, - {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, - {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, - {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, - {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, - {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, - {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, - {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, - {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, - {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, - {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, - {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, - {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, - {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, - {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, - {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, - {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, - {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, - {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, - {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, - {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, - {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, - {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, - {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, - {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, - {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, - {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, - {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, - {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, -] - [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1502,61 +941,19 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] -[[package]] -name = "nh3" -version = "0.2.21" -description = "Python binding to Ammonia HTML sanitizer Rust crate" -optional = false -python-versions = ">=3.8" -files = [ - {file = "nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286"}, - {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde"}, - {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243"}, - {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b"}, - {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251"}, - {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b"}, - {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9"}, - {file = "nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d"}, - {file = "nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82"}, - {file = "nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585"}, - {file = "nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293"}, - {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431"}, - {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa"}, - {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1"}, - {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283"}, - {file = "nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a"}, - {file = "nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629"}, - {file = "nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e"}, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - [[package]] name = "openai" version = "1.75.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "openai-1.75.0-py3-none-any.whl", hash = "sha256:fe6f932d2ded3b429ff67cc9ad118c71327db32eb9d32dd723de3acfca337125"}, {file = "openai-1.75.0.tar.gz", hash = "sha256:fb3ea907efbdb1bcfd0c44507ad9c961afd7dce3147292b54505ecfd17be8fd1"}, @@ -1583,6 +980,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1594,6 +992,7 @@ version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -1609,6 +1008,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1620,6 +1020,8 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1628,26 +1030,13 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" -[[package]] -name = "pkginfo" -version = "1.12.1.2" -description = "Query metadata from sdists / bdists / installed packages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, - {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, -] - -[package.extras] -testing = ["pytest", "pytest-cov", "wheel"] - [[package]] name = "platformdirs" version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, @@ -1658,108 +1047,13 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.14.1)"] -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "poetry" -version = "1.8.5" -description = "Python dependency management and packaging made easy." -optional = false -python-versions = "<4.0,>=3.8" -files = [ - {file = "poetry-1.8.5-py3-none-any.whl", hash = "sha256:5505fba69bf2a792b5d7402d21839c853644337392b745109b86a23010cce5f3"}, - {file = "poetry-1.8.5.tar.gz", hash = "sha256:eb2c88d224f58f36df8f7b36d6c380c07d1001bca28bde620f68fc086e881b70"}, -] - -[package.dependencies] -build = ">=1.0.3,<2.0.0" -cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]} -cleo = ">=2.1.0,<3.0.0" -crashtest = ">=0.4.1,<0.5.0" -dulwich = ">=0.21.2,<0.22.0" -fastjsonschema = ">=2.18.0,<3.0.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -installer = ">=0.7.0,<0.8.0" -keyring = ">=24.0.0,<25.0.0" -packaging = ">=23.1" -pexpect = ">=4.7.0,<5.0.0" -pkginfo = ">=1.12,<2.0" -platformdirs = ">=3.0.0,<5" -poetry-core = "1.9.1" -poetry-plugin-export = ">=1.6.0,<2.0.0" -pyproject-hooks = ">=1.0.0,<2.0.0" -requests = ">=2.26,<3.0" -requests-toolbelt = ">=1.0.0,<2.0.0" -shellingham = ">=1.5,<2.0" -tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.11.4,<1.0.0" -trove-classifiers = ">=2022.5.19" -virtualenv = ">=20.26.6,<21.0.0" -xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""} - -[[package]] -name = "poetry-core" -version = "1.9.1" -description = "Poetry PEP 517 Build Backend" -optional = false -python-versions = "<4.0,>=3.8" -files = [ - {file = "poetry_core-1.9.1-py3-none-any.whl", hash = "sha256:6f45dd3598e0de8d9b0367360253d4c5d4d0110c8f5c71120a14f0e0f116c1a0"}, - {file = "poetry_core-1.9.1.tar.gz", hash = "sha256:7a2d49214bf58b4f17f99d6891d947a9836c9899a67a5069f52d7b67217f61b8"}, -] - -[[package]] -name = "poetry-plugin-export" -version = "1.8.0" -description = "Poetry plugin to export the dependencies to various formats" -optional = false -python-versions = "<4.0,>=3.8" -files = [ - {file = "poetry_plugin_export-1.8.0-py3-none-any.whl", hash = "sha256:adbe232cfa0cc04991ea3680c865cf748bff27593b9abcb1f35fb50ed7ba2c22"}, - {file = "poetry_plugin_export-1.8.0.tar.gz", hash = "sha256:1fa6168a85d59395d835ca564bc19862a7c76061e60c3e7dfaec70d50937fc61"}, -] - -[package.dependencies] -poetry = ">=1.8.0,<3.0.0" -poetry-core = ">=1.7.0,<3.0.0" - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - [[package]] name = "prompt-toolkit" version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, @@ -1774,6 +1068,7 @@ version = "7.0.0" description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, @@ -1797,6 +1092,8 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1808,6 +1105,7 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -1816,23 +1114,14 @@ files = [ [package.extras] tests = ["pytest"] -[[package]] -name = "pycodestyle" -version = "2.13.0" -description = "Python style guide checker" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, - {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, -] - [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "implementation_name == \"pypy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1844,6 +1133,7 @@ version = "2.11.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f"}, {file = "pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3"}, @@ -1857,7 +1147,7 @@ typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1865,6 +1155,7 @@ version = "2.33.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26"}, {file = "pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927"}, @@ -1970,23 +1261,13 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pyflakes" -version = "3.3.2" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, - {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, -] - [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1995,63 +1276,13 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -description = "Wrappers to call pyproject.toml-based build backend hooks." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, - {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, -] - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "3.0.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.6" -files = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - [[package]] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2060,26 +1291,14 @@ files = [ [package.dependencies] six = ">=1.5" -[[package]] -name = "python-dotenv" -version = "1.1.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.9" -files = [ - {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, - {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - [[package]] name = "pywin32" version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, @@ -2099,23 +1318,13 @@ files = [ {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] -[[package]] -name = "pywin32-ctypes" -version = "0.2.3" -description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, - {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, -] - [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2178,6 +1387,7 @@ version = "26.4.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918"}, {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315"}, @@ -2277,137 +1487,13 @@ files = [ [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} -[[package]] -name = "rapidfuzz" -version = "3.13.0" -description = "rapid fuzzy string matching" -optional = false -python-versions = ">=3.9" -files = [ - {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107"}, - {file = "rapidfuzz-3.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f"}, - {file = "rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e"}, - {file = "rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264"}, - {file = "rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc64da907114d7a18b5e589057e3acaf2fec723d31c49e13fedf043592a3f6a7"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d9d7f84c8e992a8dbe5a3fdbea73d733da39bf464e62c912ac3ceba9c0cff93"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a79a2f07786a2070669b4b8e45bd96a01c788e7a3c218f531f3947878e0f956"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f338e71c45b69a482de8b11bf4a029993230760120c8c6e7c9b71760b6825a1"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb40ca8ddfcd4edd07b0713a860be32bdf632687f656963bcbce84cea04b8d8"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48719f7dcf62dfb181063b60ee2d0a39d327fa8ad81b05e3e510680c44e1c078"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9327a4577f65fc3fb712e79f78233815b8a1c94433d0c2c9f6bc5953018b3565"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:200030dfc0a1d5d6ac18e993c5097c870c97c41574e67f227300a1fb74457b1d"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cc269e74cad6043cb8a46d0ce580031ab642b5930562c2bb79aa7fbf9c858d26"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e62779c6371bd2b21dbd1fdce89eaec2d93fd98179d36f61130b489f62294a92"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f4797f821dc5d7c2b6fc818b89f8a3f37bcc900dd9e4369e6ebf1e525efce5db"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d21f188f6fe4fbf422e647ae9d5a68671d00218e187f91859c963d0738ccd88c"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-win32.whl", hash = "sha256:45dd4628dd9c21acc5c97627dad0bb791764feea81436fb6e0a06eef4c6dceaa"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:624a108122039af89ddda1a2b7ab2a11abe60c1521956f142f5d11bcd42ef138"}, - {file = "rapidfuzz-3.13.0-cp39-cp39-win_arm64.whl", hash = "sha256:435071fd07a085ecbf4d28702a66fd2e676a03369ee497cc38bcb69a46bc77e2"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99"}, - {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4"}, - {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ccbd0e7ea1a216315f63ffdc7cd09c55f57851afc8fe59a74184cb7316c0598b"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50856f49a4016ef56edd10caabdaf3608993f9faf1e05c3c7f4beeac46bd12a"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd05336db4d0b8348d7eaaf6fa3c517b11a56abaa5e89470ce1714e73e4aca7"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573ad267eb9b3f6e9b04febce5de55d8538a87c56c64bf8fd2599a48dc9d8b77"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fd1451f87ccb6c2f9d18f6caa483116bbb57b5a55d04d3ddbd7b86f5b14998"}, - {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6dd36d4916cf57ddb05286ed40b09d034ca5d4bca85c17be0cb6a21290597d9"}, - {file = "rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8"}, -] - -[package.extras] -all = ["numpy"] - -[[package]] -name = "readme-renderer" -version = "44.0" -description = "readme_renderer is a library for rendering readme descriptions for Warehouse" -optional = false -python-versions = ">=3.9" -files = [ - {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, - {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, -] - -[package.dependencies] -docutils = ">=0.21.2" -nh3 = ">=0.2.14" -Pygments = ">=2.5.1" - -[package.extras] -md = ["cmarkgfm (>=0.8.0)"] - [[package]] name = "regex" version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -2511,6 +1597,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2526,85 +1613,13 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "requests-toolbelt" -version = "1.0.0" -description = "A utility belt for advanced users of python-requests" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, - {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, -] - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "rfc3986" -version = "2.0.0" -description = "Validating URI References per RFC 3986" -optional = false -python-versions = ">=3.7" -files = [ - {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, - {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, -] - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "rich" -version = "14.0.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, - {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "secretstorage" -version = "3.3.3" -description = "Python bindings to FreeDesktop.org Secret Service API" -optional = false -python-versions = ">=3.6" -files = [ - {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, - {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, -] - -[package.dependencies] -cryptography = ">=2.0" -jeepney = ">=0.6" - -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2616,6 +1631,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2627,6 +1643,7 @@ version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, @@ -2638,6 +1655,7 @@ version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -2657,6 +1675,7 @@ version = "0.9.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382"}, {file = "tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108"}, @@ -2704,6 +1723,7 @@ version = "0.21.1" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41"}, {file = "tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3"}, @@ -2736,6 +1756,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -2771,23 +1793,13 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -[[package]] -name = "tomlkit" -version = "0.13.2" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, - {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, -] - [[package]] name = "tornado" version = "6.4.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, @@ -2808,6 +1820,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -2829,6 +1842,7 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -2838,49 +1852,18 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] -[[package]] -name = "trove-classifiers" -version = "2025.4.11.15" -description = "Canonical source for classifiers on PyPI (pypi.org)." -optional = false -python-versions = "*" -files = [ - {file = "trove_classifiers-2025.4.11.15-py3-none-any.whl", hash = "sha256:e7d98983f004df35293caf954bdfe944b139eb402677a97115450e320f0bd855"}, - {file = "trove_classifiers-2025.4.11.15.tar.gz", hash = "sha256:634728aa6698dc1ae3db161da94d9e4c7597a9a5da2c4410211b36f15fed60fc"}, -] - -[[package]] -name = "twine" -version = "5.0.0" -description = "Collection of utilities for publishing packages on PyPI" -optional = false -python-versions = ">=3.8" -files = [ - {file = "twine-5.0.0-py3-none-any.whl", hash = "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0"}, - {file = "twine-5.0.0.tar.gz", hash = "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4"}, -] - -[package.dependencies] -importlib-metadata = ">=3.6" -keyring = ">=15.1" -pkginfo = ">=1.8.1" -readme-renderer = ">=35.0" -requests = ">=2.20" -requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" -rfc3986 = ">=1.4.0" -rich = ">=12.0.0" -urllib3 = ">=1.26.0" - [[package]] name = "typing-extensions" version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] +markers = {dev = "python_version < \"3.11\""} [[package]] name = "typing-inspection" @@ -2888,6 +1871,7 @@ version = "0.4.0" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, @@ -2902,153 +1886,52 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "virtualenv" -version = "20.30.0" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -files = [ - {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"}, - {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - [[package]] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] -[[package]] -name = "xattr" -version = "1.1.4" -description = "Python wrapper for extended filesystem attributes" -optional = false -python-versions = ">=3.8" -files = [ - {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"}, - {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"}, - {file = "xattr-1.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:467ee77471d26ae5187ee7081b82175b5ca56ead4b71467ec2e6119d1b08beed"}, - {file = "xattr-1.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd35f46cb0154f7033f9d5d0960f226857acb0d1e0d71fd7af18ed84663007c"}, - {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d956478e9bb98a1efd20ebc6e5703497c1d2d690d5a13c4df4abf59881eed50"}, - {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f25dfdcd974b700fb04a40e14a664a80227ee58e02ea062ac241f0d7dc54b4e"}, - {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33b63365c1fcbc80a79f601575bac0d6921732e0245b776876f3db3fcfefe22d"}, - {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:544542be95c9b49e211f0a463758f200de88ba6d5a94d3c4f42855a484341acd"}, - {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac14c9893f3ea046784b7702be30889b200d31adcd2e6781a8a190b6423f9f2d"}, - {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bb4bbe37ba95542081890dd34fa5347bef4651e276647adaa802d5d0d7d86452"}, - {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3da489ecef798705f9a39ea8cea4ead0d1eeed55f92c345add89740bd930bab6"}, - {file = "xattr-1.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:798dd0cbe696635a6f74b06fc430818bf9c3b24314e1502eadf67027ab60c9b0"}, - {file = "xattr-1.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2b6361626efad5eb5a6bf8172c6c67339e09397ee8140ec41258737bea9681"}, - {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7fa20a0c9ce022d19123b1c5b848d00a68b837251835a7929fe041ee81dcd0"}, - {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20eeb08e2c57fc7e71f050b1cfae35cbb46105449853a582bf53fd23c5379e"}, - {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:477370e75821bded901487e5e752cffe554d1bd3bd4839b627d4d1ee8c95a093"}, - {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a8682091cd34a9f4a93c8aaea4101aae99f1506e24da00a3cc3dd2eca9566f21"}, - {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e079b3b1a274ba2121cf0da38bbe5c8d2fb1cc49ecbceb395ce20eb7d69556d"}, - {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ae6579dea05bf9f335a082f711d5924a98da563cac72a2d550f5b940c401c0e9"}, - {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd6038ec9df2e67af23c212693751481d5f7e858156924f14340376c48ed9ac7"}, - {file = "xattr-1.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:608b2877526674eb15df4150ef4b70b7b292ae00e65aecaae2f192af224be200"}, - {file = "xattr-1.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54dad1a6a998c6a23edfd25e99f4d38e9b942d54e518570044edf8c767687ea"}, - {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0dab6ff72bb2b508f3850c368f8e53bd706585012676e1f71debba3310acde8"}, - {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3c54c6af7cf09432b2c461af257d5f4b1cb2d59eee045f91bacef44421a46d"}, - {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e346e05a158d554639fbf7a0db169dc693c2d2260c7acb3239448f1ff4a9d67f"}, - {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3ff6d9e2103d0d6e5fcd65b85a2005b66ea81c0720a37036445faadc5bbfa424"}, - {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a2ee4563c6414dfec0d1ac610f59d39d5220531ae06373eeb1a06ee37cd193f"}, - {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878df1b38cfdadf3184ad8c7b0f516311128d5597b60ac0b3486948953658a83"}, - {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c9b8350244a1c5454f93a8d572628ff71d7e2fc2f7480dcf4c4f0e8af3150fe"}, - {file = "xattr-1.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a46bf48fb662b8bd745b78bef1074a1e08f41a531168de62b5d7bd331dadb11a"}, - {file = "xattr-1.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83fc3c07b583777b1dda6355329f75ca6b7179fe0d1002f1afe0ef96f7e3b5de"}, - {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6308b19cff71441513258699f0538394fad5d66e1d324635207a97cb076fd439"}, - {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c00ddc15ddadc9c729cd9504dabf50adb3d9c28f647d4ac9a3df45a046b1a0"}, - {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a06136196f26293758e1b244200b73156a0274af9a7349fa201c71c7af3bb9e8"}, - {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8fc2631a3c6cfcdc71f7f0f847461839963754e76a2015de71e7e71e3304abc0"}, - {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6e1e835f9c938d129dd45e7eb52ebf7d2d6816323dab93ce311bf331f7d2328"}, - {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60dea2d369a6484e8b7136224fc2971e10e2c46340d83ab780924afe78c90066"}, - {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85c2b778b09d919523f80f244d799a142302582d76da18903dc693207c4020b0"}, - {file = "xattr-1.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee0abba9e1b890d39141714ff43e9666864ca635ea8a5a2194d989e6b17fe862"}, - {file = "xattr-1.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4174ba7f51f46b95ea7918d907c91cd579575d59e6a2f22ca36a0551026737"}, - {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2b05e52e99d82d87528c54c2c5c8c5fb0ba435f85ac6545511aeea136e49925"}, - {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a3696fad746be37de34eb73c60ea67144162bd08106a5308a90ce9dea9a3287"}, - {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3a7149439a26b68904c14fdc4587cde4ac7d80303e9ff0fefcfd893b698c976"}, - {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:507b36a126ce900dbfa35d4e2c2db92570c933294cba5d161ecd6a89f7b52f43"}, - {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9392b417b54923e031041940d396b1d709df1d3779c6744454e1f1c1f4dad4f5"}, - {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e9f00315e6c02943893b77f544776b49c756ac76960bea7cb8d7e1b96aefc284"}, - {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8f98775065260140efb348b1ff8d50fd66ddcbf0c685b76eb1e87b380aaffb3"}, - {file = "xattr-1.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b471c6a515f434a167ca16c5c15ff34ee42d11956baa749173a8a4e385ff23e7"}, - {file = "xattr-1.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0763a1b7ceb78ba2f78bee5f30d1551dc26daafcce4ac125115fa1def20519"}, - {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:099e6e9ce7999b403d36d9cf943105a3d25d8233486b54ec9d1b78623b050433"}, - {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e56faef9dde8d969f0d646fb6171883693f88ae39163ecd919ec707fbafa85"}, - {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:328156d4e594c9ae63e1072503c168849e601a153ad37f0290743544332d6b6f"}, - {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a57a55a27c7864d6916344c9a91776afda6c3b8b2209f8a69b79cdba93fbe128"}, - {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c19cdde08b040df1e99d2500bf8a9cff775ab0e6fa162bf8afe6d84aa93ed04"}, - {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c72667f19d3a9acf324aed97f58861d398d87e42314731e7c6ab3ac7850c971"}, - {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:67ae934d75ea2563fc48a27c5945749575c74a6de19fdd38390917ddcb0e4f24"}, - {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1b0c348dd8523554dc535540d2046c0c8a535bb086561d8359f3667967b6ca"}, - {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22284255d2a8e8f3da195bd8e8d43ce674dbc7c38d38cb6ecfb37fae7755d31f"}, - {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aac5ef4381c26d3ce147ca98fba5a78b1e5bcd6be6755b4908659f2705c6d"}, - {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:803f864af528f6f763a5be1e7b1ccab418e55ae0e4abc8bda961d162f850c991"}, - {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:40354ebfb5cecd60a5fbb9833a8a452d147486b0ffec547823658556625d98b5"}, - {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2abaf5d06be3361bfa8e0db2ee123ba8e92beab5bceed5e9d7847f2145a32e04"}, - {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e638e5ffedc3565242b5fa3296899d35161bad771f88d66277b58f03a1ba9fe"}, - {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0597e919d116ec39997804288d77bec3777228368efc0f2294b84a527fc4f9c2"}, - {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee9455c501d19f065527afda974418b3ef7c61e85d9519d122cd6eb3cb7a00"}, - {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:89ed62ce430f5789e15cfc1ccabc172fd8b349c3a17c52d9e6c64ecedf08c265"}, - {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b824f4b9259cd8bb6e83c4873cf8bf080f6e4fa034a02fe778e07aba8d345"}, - {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fba66faa0016dfc0af3dd7ac5782b5786a1dfb851f9f3455e266f94c2a05a04"}, - {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec4b0c3e0a7bcd103f3cf31dd40c349940b2d4223ce43d384a3548992138ef1"}, - {file = "xattr-1.1.4.tar.gz", hash = "sha256:b7b02ecb2270da5b7e7deaeea8f8b528c17368401c2b9d5f63e91f545b45d372"}, -] - -[package.dependencies] -cffi = ">=1.16.0" - -[package.extras] -test = ["pytest"] - [[package]] name = "zipp" version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version < \"3.10\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.9" -content-hash = "8a7ab1739b3561d12df6102bd32722423e742c20db84ec36c3439b48f8ceffb1" +content-hash = "d0b17a80d3318ef42785a6ded350d03c86ccccff4721d09b4bf3a68561296968" diff --git a/pyproject.toml b/pyproject.toml index c62a6c2..173684e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ anthropic = "^0.31.2" httpx = "^0.27.2" beautifulsoup4 = "^4.13.4" -[tool.poetry.dev-dependencies] +[poetry.group.dev.dependencies] poetry = "^1.7.1" isort = "^5.13.2" flake8 = "^7.0.0" diff --git a/tests/prompts/test_pricing.py b/tests/prompts/test_pricing.py index 989ebc9..f2c9b86 100644 --- a/tests/prompts/test_pricing.py +++ b/tests/prompts/test_pricing.py @@ -59,4 +59,4 @@ def test_claude_pricing(lamoom_client: Lamoom): result_haiku = lamoom_client.call(prompt.id, context, "claude/claude-3-5-haiku-latest", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) result_sonnet = lamoom_client.call(prompt.id, context, "claude/claude-3-5-sonnet-latest", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - assert result_sonnet.metrics.price_of_call > result_haiku.metrics.price_of_call \ No newline at end of file + assert result_sonnet.metrics.price_of_call >= result_haiku.metrics.price_of_call \ No newline at end of file From b36d7142c37ebcf84808fa154aef0f8a4f8fff9a Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 7 May 2025 17:23:22 -0700 Subject: [PATCH 10/34] updated --- lamoom/ai_models/ai_model.py | 271 +++++++++-------------- lamoom/ai_models/claude/claude_model.py | 202 +++++------------ lamoom/ai_models/openai/openai_models.py | 111 +--------- 3 files changed, 162 insertions(+), 422 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 84b96e8..717be4a 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,15 +1,14 @@ import typing as t from dataclasses import dataclass, field from enum import Enum - +import logging from _decimal import Decimal -from lamoom.ai_models.tools.base_tool import ToolDefinition -from lamoom.responses import AIResponse - -from logging import getLogger +from lamoom.ai_models.tools.base_tool import ToolDefinition, inject_tool_prompts, parse_tool_call_block +from lamoom.responses import AIResponse, StreamingResponse +from lamoom.exceptions import RetryableCustomError -logger = getLogger(__name__) +logger = logging.getLogger(__name__) class AI_MODELS_PROVIDER(Enum): OPENAI = "openai" @@ -36,179 +35,109 @@ def _decimal(self, value) -> Decimal: def get_params(self) -> t.Dict[str, t.Any]: return {} - def call(self, *args, **kwargs) -> AIResponse: - raise NotImplementedError - def get_metrics_data(self): return {} - - - -@dataclass(kw_only=True) -class StreamResponse(AIResponse): - stream_function: t.Callable - check_connection: t.Callable - stream_params: dict - - client: AIModel - tool_registry: t.Dict[str, ToolDefinition] - max_tool_iterations: int - initial_call_kwargs: dict - initial_messages_history: t.List[dict] - first_response_to_user_ts: t.Optional[int] = None - exception: t.Optional[Exception] = None - - # Internal state for the stream method - _current_messages_history: t.List[dict] = field(init=False, default_factory=list) - _total_accumulated_content: str = field(init=False, default="") - - def __post_init__(self): - self._current_messages_history = list(self.initial_messages_history) - - def process_message(self, text: str, idx: int): - if idx % 5 == 0: - if not self.check_connection(**self.stream_params): - raise ConnectionLostError("Connection was lost!") - if not text: - return - self.stream_function(text, **self.stream_params) - - def _handle_tool_call(self, tool_name: str, parameters: dict) -> str: - """Handle a tool call by executing the corresponding function from the MCP registry.""" - tool_function = self.mcp_call_registry.get(tool_name) - if not tool_function: - logger.warning(f"Tool '{tool_name}' not found in MCP registry") - return json.dumps({"error": f"Tool '{tool_name}' is not available."}) - - try: - logger.info(f"Executing MCP tool '{tool_name}' with parameters: {parameters}") - result = tool_function(**parameters) - logger.info(f"MCP tool '{tool_name}' executed successfully") - return json.dumps({"result": result}) - except Exception as e: - logger.exception(f"Error executing MCP tool '{tool_name}'", exc_info=e) - return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) - - def _process_stream_response(self, stream_iterator) -> t.Tuple[str, str, t.Optional[dict]]: - """Process a single streaming response from OpenAI. + def call( + self, + messages: t.List[t.Dict[str, str]], + max_tokens: t.Optional[int], + tool_registry: t.Dict[str, ToolDefinition] = {}, + max_tool_iterations: int = 5, # Safety limit for sequential calls + stream_function: t.Callable = None, + check_connection: t.Callable = None, + stream_params: dict = {}, + client_secrets: dict = {}, + **kwargs, + ) -> AIResponse: + """Common call implementation that handles streaming and tool calls.""" + client = self.get_client(client_secrets) + tool_definitions = list(tool_registry.values()) - Returns: - Tuple of (content, stop_reason, detected_tool_call) - """ - current_stream_part_content = "" - stream_stop_reason = None - detected_tool_call = None - stream_idx = 0 - - try: - logger.debug("Processing stream chunks...") - content = "" - for i, data in enumerate(self.original_result): - if not data.choices: - continue - msg = data.choices[0] - delta = msg.delta - if delta: - content += delta.content or "" - self.process_message(delta.content, i) - - if '' in delta: - detected_tool_call = parse_tool_call_block(delta.content) - if detected_tool_call: - logger.info(f"Detected tool call: {detected_tool_call}") - break - - if 'content' in delta: - if not first_response_to_user_ts: - first_response_to_user_ts = curr_timestamp_in_ms() - chat_stream_response.first_response_to_user_ts = ( - first_response_to_user_ts - ) - generated_text = delta.content - chat_stream_response.content += generated_text or '' - process_messages(generated_text, i) - - if message.choices and 'finish_reason' in message.choices[0]: - finish_reason = message.choices[0].finish_reason - chat_stream_response.message = { - 'role': 'assistant', - 'content': chat_stream_response.content, - } - chat_stream_response.finish_reason = finish_reason - return chat_stream_response - - except ConnectionLostError: - raise - except Exception as e: - logger.exception("Exception during stream processing", exc_info=e) - raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e - - return current_stream_part_content, stream_stop_reason, detected_tool_call - - def stream(self) -> t.Self: - """Process the stream, handle tool calls, and manage conversation history.""" - iteration_count = 0 - current_stream_iterator = self.original_result - - while iteration_count < self.max_tool_iterations: - iteration_count += 1 - logger.info(f"--- OpenAI Tool Streaming Iteration: {iteration_count} ---") - + # Prepare streaming response + stream_response = StreamingResponse( + tool_registry=tool_registry, + messages=messages + ) + + # Inject tool prompts into first message + current_messages = inject_tool_prompts(messages, tool_definitions) + + attempts = max_tool_iterations + while attempts > 0: try: - current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( - current_stream_iterator + stream_response = self.streaming( + client=client, + messages=current_messages, + max_tokens=max_tokens, + stream_function=stream_function, + check_connection=check_connection, + stream_params=stream_params, + stream_response=stream_response, + **kwargs ) - - # Add the raw assistant response to history - assistant_message = {"role": "assistant", "content": current_stream_part_content} - self._current_messages_history.append(assistant_message) - - if detected_tool_call: - tool_name = detected_tool_call.get("tool_name") - parameters = detected_tool_call.get("parameters", {}) + + if stream_response.is_detected_tool_call: + parsed_tool_call = parse_tool_call_block(stream_response.detected_tool_call) + if not parsed_tool_call: + continue + + # Execute tool call + tool_result = self.handle_tool_call(parsed_tool_call, tool_registry) - # Execute the tool and get result - tool_result_str = self._handle_tool_call(tool_name, parameters) + # Add messages to history + stream_response.add_message("assistant", stream_response.content) + stream_response.add_tool_result(parsed_tool_call, tool_result) - # Add tool result to history - tool_result_message = format_tool_result_message(tool_name, tool_result_str) - self._current_messages_history.append(tool_result_message) - - # Make a new streaming call with updated history - logger.info("Restarting stream after tool execution") - new_call_kwargs = { - **self.initial_call_kwargs, - "messages": self._current_messages_history, - "stream": True, - } - current_stream_iterator = self.client.chat.completions.create(**new_call_kwargs) + # Update messages for next iteration + current_messages = stream_response.messages + attempts -= 1 continue - - # No tool call found - finish streaming - self._total_accumulated_content = current_stream_part_content - self.finish_reason = stream_stop_reason or "stop" + break + + except RetryableCustomError: + attempts -= 1 + continue + + return stream_response + + def handle_tool_call(self, tool_call: dict, tool_registry: t.Dict[str, ToolDefinition]) -> str: + """Handle a tool call by executing the corresponding function from the registry.""" + tool_name = tool_call.get("tool_name") + parameters = tool_call.get("parameters", {}) + + tool_function = tool_registry.get(tool_name) + if not tool_function: + logger.warning(f"Tool '{tool_name}' not found in registry") + return json.dumps({"error": f"Tool '{tool_name}' is not available."}) + + try: + logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + result = tool_function.execution_function(**parameters) + logger.info(f"Tool '{tool_name}' executed successfully") + return json.dumps({"result": result}) + except Exception as e: + logger.exception(f"Error executing tool '{tool_name}'", exc_info=e) + return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) - except ConnectionLostError as cle: - logger.error("Connection lost during stream processing", exc_info=cle) - self.finish_reason = "error_connection_lost" - raise cle - except Exception as e: - logger.exception("Exception during stream processing", exc_info=e) - self.finish_reason = "error_processing_stream" - raise_openai_exception(e) - - if iteration_count >= self.max_tool_iterations: - logger.warning(f"Reached maximum tool call iterations ({self.max_tool_iterations})") - self.finish_reason = "error_max_tool_iterations" - - # Set final response fields - self.content = self._total_accumulated_content - self.message = Message( - content=self.content, - role="assistant" - ) - - logger.debug(f"Stream processing complete. Final finish reason: {self.finish_reason}") - return self \ No newline at end of file + def streaming( + self, + client: t.Any, + messages: t.List[dict], + max_tokens: int, + stream_function: t.Callable, + check_connection: t.Callable, + stream_params: dict, + stream_response: StreamingResponse, + **kwargs + ) -> StreamingResponse: + """Process streaming response. Must be implemented by subclasses.""" + raise NotImplementedError("Subclasses must implement streaming method") + + def get_client(self, client_secrets: dict = {}) -> t.Any: + """Get the client instance. Must be implemented by subclasses.""" + raise NotImplementedError("Subclasses must implement get_client method") + + + \ No newline at end of file diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index d47c92b..df36ad6 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -1,17 +1,15 @@ from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER, AIModel import logging +import typing as t +from dataclasses import dataclass +import json from lamoom.ai_models.claude.constants import HAIKU, SONNET, OPUS from lamoom.ai_models.constants import C_4K -from lamoom.responses import AIResponse -from lamoom.ai_models.tools.base_tool import ToolDefinition, handle_tool_call, inject_tool_prompts +from lamoom.responses import AIResponse, StreamingResponse +from lamoom.ai_models.tools.base_tool import ToolDefinition from enum import Enum -import typing as t -from dataclasses import dataclass - -from lamoom.ai_models.utils import get_common_args - from lamoom.exceptions import RetryableCustomError, ConnectionLostError import anthropic @@ -49,7 +47,7 @@ def __post_init__(self): def get_client(self, client_secrets: dict) -> anthropic.Anthropic: return anthropic.Anthropic(api_key=client_secrets.get("api_key")) - def uny_all_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict]: + def unify_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict]: result = [] last_role = None for message in messages: @@ -62,149 +60,67 @@ def uny_all_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict result[-1]["content"] += message.get("content") return result - def _process_stream_response(self, - client: anthropic.Anthropic, - current_messages_history: t.List[dict], - max_tokens: int, - stream_function: t.Callable, - check_connection: t.Callable, - stream_params: dict, - tool_registry: t.Dict[str, ToolDefinition]) -> t.Tuple[str, str, bool]: - """Process a single streaming response from Claude. - - Args: - client: Anthropic client instance - current_messages_history: Current conversation history - max_tokens: Maximum tokens to generate - system_prompt: Optional system prompt - stream_function: Function to call for each text chunk - check_connection: Function to check connection status - stream_params: Parameters for stream function - tool_registry: Registry of available MCP functions - - Returns: - Tuple of (content, stop_reason, has_tool_call) - """ - current_stream_part_content = "" - stream_stop_reason = None - detected_tool_call = None - - call_kwargs = { - "model": self.model, - "max_tokens": max_tokens, - "messages": current_messages_history, - } - system_prompt = [] - for i, msg in enumerate(current_messages_history): - if msg.get('role') == "system": - system_prompt += [current_messages_history.pop(i - len(system_prompt)).get('content')] + def streaming( + self, + client: anthropic.Anthropic, + messages: t.List[dict], + max_tokens: int, + stream_function: t.Callable, + check_connection: t.Callable, + stream_params: dict, + stream_response: StreamingResponse, + **kwargs + ) -> StreamingResponse: + """Process streaming response from Claude.""" + tool_call_started = False + content = "" - if system_prompt: - call_kwargs["system"] = '\n'.join(system_prompt) - - stream_idx = 0 try: - logger.debug(f"Initiating Claude stream. History length: {len(current_messages_history)}") - with client.messages.stream(**call_kwargs) as stream_handler: - current_stream_part_content = '' - for text_chunk in stream_handler.text_stream: - stream_idx += 1 - if check_connection and stream_idx % 5 == 0 and not check_connection(**stream_params): + # Prepare messages for Claude + unified_messages = self.unify_messages_with_same_role(messages) + + call_kwargs = { + "model": self.model, + "max_tokens": max_tokens, + "messages": unified_messages, + **kwargs + } + + # Extract system prompt if present + system_prompt = [] + for i, msg in enumerate(unified_messages): + if msg.get('role') == "system": + system_prompt.append(unified_messages.pop(i- len(system_prompt)).get('content')) + + if system_prompt: + call_kwargs["system"] = '\n'.join(system_prompt) + + with client.messages.stream(**call_kwargs) as stream: + for text_chunk in stream.text_stream: + if check_connection and not check_connection(**stream_params): raise ConnectionLostError("Connection was lost!") - - current_stream_part_content += text_chunk - if stream_function and text_chunk: + + content += text_chunk + if stream_function: stream_function(text_chunk, **stream_params) - # Check for tool call after each chunk - detected_tool_call = handle_tool_call( - current_stream_part_content, tool_registry - ) - if detected_tool_call: - logger.info(f'Found tool {detected_tool_call} in the response') - break # Stop streaming when tool call is detected - - if not detected_tool_call: - final_message_status = stream_handler.get_final_message() - stream_stop_reason = final_message_status.stop_reason - logger.debug(f"Claude stream part finished. Stop Reason: {stream_stop_reason}") - - else: - current_messages_history.append( - {"role": "assistant", "content": current_stream_part_content + detected_tool_call.execution_result} - ) - except ConnectionLostError: - raise - except anthropic.APIError as e: - logger.exception("[CLAUDEAI] API Error during stream processing", exc_info=e) - raise RetryableCustomError(f"Claude AI API Error: {e}") from e + # Check for tool call markers + if tool_call_started and "" in content: + stream_response.is_detected_tool_call = True + stream_response.detected_tool_call = content + break + if "" in content: + if not tool_call_started: + tool_call_started = True + continue + stream_response.content = content + return stream_response + except Exception as e: - logger.exception("Exception during Claude stream processing", exc_info=e) + stream_response.content = content + logger.exception("Exception during stream processing", exc_info=e) raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e - return current_stream_part_content, stream_stop_reason, detected_tool_call - - - def call(self, - messages: t.List[dict], - max_tokens: int, - client_secrets: dict = {}, - tool_registry: t.Dict[str, ToolDefinition] = {}, - max_tool_iterations: int = 5, # Safety limit for sequential calls - **kwargs) -> AIResponse: - max_tokens = min(max_tokens, self.max_tokens) - - common_args = get_common_args(max_tokens) - kwargs = { - **common_args, - **self.get_params(), - **kwargs, - } - - # Inject Tool Prompts into initial messages - current_messages_history = inject_tool_prompts(messages, list(tool_registry.values())) - - - logger.debug( - f"Calling {current_messages_history} with max_tokens {max_tokens} and kwargs {kwargs}" - ) - client = self.get_client(client_secrets) - - stream_function = kwargs.get("stream_function") - check_connection = kwargs.get("check_connection") - stream_params = kwargs.get("stream_params") - - content = "" - iteration_count = 0 - while iteration_count < max_tool_iterations: - iteration_count += 1 - try: - logger.info(f"--- Custom Claude Tool Streaming Iteration: {iteration_count} ---") - - current_stream_part_content, stream_stop_reason, detected_tool_call = self._process_stream_response( - client, current_messages_history, max_tokens, - stream_function, check_connection, stream_params, tool_registry - ) - - # Add the raw assistant response to history *before* parsing - assistant_message_to_add = {"role": "assistant", "content": current_stream_part_content} - current_messages_history.append(assistant_message_to_add) - - if detected_tool_call: - logger.info(f'Found {detected_tool_call}') - continue - - # --- No Tool Call Found in this stream part --- - content += current_stream_part_content - if stream_stop_reason == "end_turn": - break - except Exception as e: - logger.exception("Exception during Claude API call", exc_info=e) - raise RetryableCustomError(f"Claude AI API call failed: {e}") from e - - logger.info(f"Returning: {content}") - return AIResponse(content=content) - @property def name(self) -> str: return f"Claude {self.family}" diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 4cad216..caa46c1 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -88,36 +88,6 @@ def get_metrics_data(self): "base_url": self.get_base_url() if self.base_url is None else self.base_url } - def call( - self, - messages, - max_tokens, - stream_function: t.Callable = None, - check_connection: t.Callable = None, - stream_params: dict = {}, - client_secrets: dict = {}, - **kwargs, - ) -> OpenAIResponse: - logger.debug( - f"Calling {messages} with max_tokens {max_tokens} and kwargs {kwargs}" - ) - if self.family in [ - FamilyModel.chat.value, - FamilyModel.gpt4.value, - FamilyModel.gpt4o.value, - FamilyModel.gpt4o_mini.value, - ]: - return self.call_chat_completion( - messages, - max_tokens, - stream_function=stream_function, - check_connection=check_connection, - stream_params=stream_params, - client_secrets=client_secrets, - **kwargs, - ) - raise NotImplementedError(f"Openai family {self.family} is not implemented") - def get_client(self, client_secrets: dict = {}): return OpenAI( organization=client_secrets.get("organization", None), @@ -125,84 +95,6 @@ def get_client(self, client_secrets: dict = {}): base_url=self.get_base_url() if self.base_url is None else self.base_url ) - def call_chat_completion( - self, - messages: t.List[t.Dict[str, str]], - max_tokens: t.Optional[int], - tool_registry: t.Dict[str, ToolDefinition] = {}, - max_tool_iterations: int = 5, # Safety limit for sequential calls - stream_function: t.Callable = None, - check_connection: t.Callable = None, - stream_params: dict = {}, - client_secrets: dict = {}, - **kwargs, - ) -> OpenAIResponse: - client = self.get_client(client_secrets) - tool_definitions = list(tool_registry.values()) - - # Prepare streaming response - stream_response = StreamingResponse( - tool_registry=tool_registry, - messages=messages - ) - - # Inject tool prompts into first message - current_messages = inject_tool_prompts(messages, tool_definitions) - - attempts = max_tool_iterations - while attempts > 0: - try: - stream_response = self._streaming( - client=client, - messages=current_messages, - max_tokens=max_tokens, - stream_function=stream_function, - check_connection=check_connection, - stream_params=stream_params, - stream_response=stream_response, - **kwargs - ) - - if stream_response.is_detected_tool_call: - parsed_tool_call = parse_tool_call_block(stream_response.detected_tool_call) - if not parsed_tool_call: - continue - - # Execute tool call - tool_result = self.handle_tool_call(parsed_tool_call, tool_registry) - - # Add messages to history - stream_response.add_message("assistant", stream_response.content) - stream_response.add_tool_result(parsed_tool_call, tool_result) - - # Update messages for next iteration - current_messages = stream_response.messages - attempts -= 1 - continue - - break - - except RetryableCustomError: - attempts -= 1 - continue - - return stream_response - - def handle_tool_call(self, tool_name: str, parameters: dict, tool_registry: t.Dict[str, ToolDefinition]) -> str: - """Handle a tool call by executing the corresponding function from the registry.""" - tool_function = tool_registry.get(tool_name) - if not tool_function: - logger.warning(f"Tool '{tool_name}' not found in registry") - return json.dumps({"error": f"Tool '{tool_name}' is not available."}) - try: - logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") - result = tool_function.execution_function(**parameters) - logger.info(f"Tool '{tool_name}' executed successfully") - return json.dumps({"result": result}) - except Exception as e: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=e) - return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) - def streaming( self, client: OpenAI, @@ -217,6 +109,7 @@ def streaming( """Process streaming response from OpenAI.""" tool_call_started = False content = "" + try: call_kwargs = { "messages": messages, @@ -252,8 +145,10 @@ def streaming( stream_response.is_detected_tool_call = True stream_response.detected_tool_call = content break + stream_response.content = content return stream_response + except Exception as e: stream_response.content = content logger.exception("Exception during stream processing", exc_info=e) From f9038e79ada5af9c1d3e0b5bbcbd20dd91f18a0e Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 7 May 2025 20:47:31 -0700 Subject: [PATCH 11/34] testing --- README.md | 8 +-- .../evaluate_prompt_quality.py | 2 +- lamoom/ai_models/ai_model.py | 58 +++++++--------- lamoom/ai_models/claude/claude_model.py | 19 +++--- lamoom/ai_models/openai/azure_models.py | 4 +- lamoom/ai_models/openai/openai_models.py | 23 ++++--- lamoom/ai_models/openai/responses.py | 14 +--- lamoom/ai_models/tools/base_tool.py | 20 ++---- lamoom/responses.py | 68 +++++++++++++++++++ tests/conftest.py | 65 ------------------ tests/prompts/test_model.py | 2 +- tests/prompts/test_pricing.py | 10 +-- tests/prompts/test_prompt.py | 2 +- tests/prompts/test_stream.py | 3 +- tests/prompts/test_web_call.py | 8 ++- tests/test_integrational.py | 2 +- 16 files changed, 147 insertions(+), 161 deletions(-) diff --git a/README.md b/README.md index fd5d81d..e85858c 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ For Azure models format is the following: `"azure/{realm}/{model_name}"` ```python -response_llm = client.call(agent.id, context, model = "openai/gpt-4o") +response_llm = client.call(agent.id, context, model = "openai/o4-mini") response_llm = client.call(agent.id, context, model = "azure/useast/gpt-4o") ``` @@ -141,7 +141,7 @@ Custom model string format is the following: `provider_url` is required ```python -response_llm = client.call(agent.id, context, model = "custom/gpt-4o", provider_url = "your_model_url") +response_llm = client.call(agent.id, context, model = "custom/o4-mini", provider_url = "your_model_url") ``` ### Lamoom Keys @@ -169,14 +169,14 @@ prompt.add("You're {name}. Say Hello and ask what's their name.", role="system") # Call AI model with Lamoom context = {"name": "John Doe"} -response = client.call(prompt.id, context, "openai/gpt-4o") +response = client.call(prompt.id, context, "openai/o4-mini") print(response.content) ``` ### Creating Tests While Using Prompts ```python # Call with test_data to automatically generate tests -response = client.call(prompt.id, context, "openai/gpt-4o", test_data={ +response = client.call(prompt.id, context, "openai/o4-mini", test_data={ 'ideal_answer': "Hello, I'm John Doe. What's your name?", 'model_name': "gemini/gemini-1.5-flash" }) diff --git a/docs/evaluate_prompts_quality/evaluate_prompt_quality.py b/docs/evaluate_prompts_quality/evaluate_prompt_quality.py index 2d1f925..f775e5d 100644 --- a/docs/evaluate_prompts_quality/evaluate_prompt_quality.py +++ b/docs/evaluate_prompts_quality/evaluate_prompt_quality.py @@ -14,7 +14,7 @@ AttemptToCall( ai_model=AzureAIModel( realm='useast', - deployment_id="gpt-4o", + deployment_id="o4-mini", max_tokens=C_128K, support_functions=True, ), diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 717be4a..47cdce8 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,10 +1,11 @@ +import json import typing as t -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum import logging from _decimal import Decimal -from lamoom.ai_models.tools.base_tool import ToolDefinition, inject_tool_prompts, parse_tool_call_block +from lamoom.ai_models.tools.base_tool import ToolCallResult, ToolDefinition, inject_tool_prompts, parse_tool_call_block from lamoom.responses import AIResponse, StreamingResponse from lamoom.exceptions import RetryableCustomError @@ -53,59 +54,53 @@ def call( """Common call implementation that handles streaming and tool calls.""" client = self.get_client(client_secrets) tool_definitions = list(tool_registry.values()) - + # Inject tool prompts into first message + current_messages = inject_tool_prompts(messages, tool_definitions) # Prepare streaming response stream_response = StreamingResponse( tool_registry=tool_registry, - messages=messages + messages=current_messages ) - - # Inject tool prompts into first message - current_messages = inject_tool_prompts(messages, tool_definitions) - attempts = max_tool_iterations while attempts > 0: try: stream_response = self.streaming( client=client, - messages=current_messages, + stream_response=stream_response, max_tokens=max_tokens, stream_function=stream_function, check_connection=check_connection, stream_params=stream_params, - stream_response=stream_response, **kwargs ) - + logger.info(f'stream_response: {stream_response}') if stream_response.is_detected_tool_call: - parsed_tool_call = parse_tool_call_block(stream_response.detected_tool_call) + logger.info(f'is_detected_tool_call') + parsed_tool_call = parse_tool_call_block(stream_response.content) + logger.info(f'parsed_tool_call {parsed_tool_call}') if not parsed_tool_call: continue - # Execute tool call - tool_result = self.handle_tool_call(parsed_tool_call, tool_registry) - + self.handle_tool_call(parsed_tool_call, tool_registry) # Add messages to history + logger.info(f'executed parsed_tool_call {parsed_tool_call}') stream_response.add_message("assistant", stream_response.content) - stream_response.add_tool_result(parsed_tool_call, tool_result) - + stream_response.add_tool_result(parsed_tool_call) + logger.info(f'Added message {stream_response.messages}') # Update messages for next iteration - current_messages = stream_response.messages attempts -= 1 continue - break - except RetryableCustomError: attempts -= 1 - continue - + continue return stream_response - def handle_tool_call(self, tool_call: dict, tool_registry: t.Dict[str, ToolDefinition]) -> str: + + def handle_tool_call(self, tool_call: ToolCallResult, tool_registry: t.Dict[str, ToolDefinition]) -> str: """Handle a tool call by executing the corresponding function from the registry.""" - tool_name = tool_call.get("tool_name") - parameters = tool_call.get("parameters", {}) + tool_name = tool_call.tool_name + parameters = tool_call.parameters tool_function = tool_registry.get(tool_name) if not tool_function: @@ -116,20 +111,22 @@ def handle_tool_call(self, tool_call: dict, tool_registry: t.Dict[str, ToolDefin logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") result = tool_function.execution_function(**parameters) logger.info(f"Tool '{tool_name}' executed successfully") + tool_call.execution_result = result return json.dumps({"result": result}) except Exception as e: - logger.exception(f"Error executing tool '{tool_name}'", exc_info=e) - return json.dumps({"error": f"Failed to execute tool '{tool_name}': {str(e)}"}) + result = f"Error executing tool '{tool_name}'" + logger.exception(result, exc_info=e) + tool_call.execution_result = result + return json.dumps({"error": f"{result}: {str(e)}"}) def streaming( self, client: t.Any, - messages: t.List[dict], + stream_response: StreamingResponse, max_tokens: int, stream_function: t.Callable, check_connection: t.Callable, stream_params: dict, - stream_response: StreamingResponse, **kwargs ) -> StreamingResponse: """Process streaming response. Must be implemented by subclasses.""" @@ -138,6 +135,3 @@ def streaming( def get_client(self, client_secrets: dict = {}) -> t.Any: """Get the client instance. Must be implemented by subclasses.""" raise NotImplementedError("Subclasses must implement get_client method") - - - \ No newline at end of file diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index df36ad6..67431a1 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -2,12 +2,11 @@ import logging import typing as t from dataclasses import dataclass -import json from lamoom.ai_models.claude.constants import HAIKU, SONNET, OPUS from lamoom.ai_models.constants import C_4K -from lamoom.responses import AIResponse, StreamingResponse -from lamoom.ai_models.tools.base_tool import ToolDefinition +from lamoom.responses import FINISH_REASON_ERROR, FINISH_REASON_FINISH, StreamingResponse +from lamoom.ai_models.tools.base_tool import TOOL_CALL_END_TAG, TOOL_CALL_START_TAG from enum import Enum from lamoom.exceptions import RetryableCustomError, ConnectionLostError @@ -63,21 +62,21 @@ def unify_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict]: def streaming( self, client: anthropic.Anthropic, - messages: t.List[dict], + stream_response: StreamingResponse, max_tokens: int, stream_function: t.Callable, check_connection: t.Callable, stream_params: dict, - stream_response: StreamingResponse, **kwargs ) -> StreamingResponse: """Process streaming response from Claude.""" tool_call_started = False content = "" + stream_response.finish_reason = FINISH_REASON_FINISH try: # Prepare messages for Claude - unified_messages = self.unify_messages_with_same_role(messages) + unified_messages = self.unify_messages_with_same_role(stream_response.messages) call_kwargs = { "model": self.model, @@ -105,11 +104,12 @@ def streaming( stream_function(text_chunk, **stream_params) # Check for tool call markers - if tool_call_started and "" in content: + if tool_call_started and TOOL_CALL_END_TAG in content: stream_response.is_detected_tool_call = True - stream_response.detected_tool_call = content + stream_response.content = content + logger.info(f'Found tool call request in {content}') break - if "" in content: + if TOOL_CALL_START_TAG in content: if not tool_call_started: tool_call_started = True continue @@ -118,6 +118,7 @@ def streaming( except Exception as e: stream_response.content = content + stream_response.finish_reason = FINISH_REASON_ERROR logger.exception("Exception during stream processing", exc_info=e) raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e diff --git a/lamoom/ai_models/openai/azure_models.py b/lamoom/ai_models/openai/azure_models.py index 736831f..f0b7f26 100644 --- a/lamoom/ai_models/openai/azure_models.py +++ b/lamoom/ai_models/openai/azure_models.py @@ -26,9 +26,9 @@ def __post_init__(self): self.family = FamilyModel.instruct_gpt.value elif self.deployment_id.startswith(("gpt3", "gpt-3")): self.family = FamilyModel.chat.value - elif self.deployment_id.startswith("gpt-4o-mini"): + elif self.deployment_id.startswith("o4-mini-mini"): self.family = FamilyModel.gpt4o_mini.value - elif self.deployment_id.startswith("gpt-4o"): + elif self.deployment_id.startswith("o4-mini"): self.family = FamilyModel.gpt4o.value elif self.deployment_id.startswith(("gpt4", "gpt-4", "gpt")): self.family = FamilyModel.gpt4.value diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index caa46c1..59ceac4 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -15,7 +15,7 @@ import json from openai.types.chat import ChatCompletionMessage as Message -from lamoom.responses import Prompt +from lamoom.responses import FINISH_REASON_ERROR, Prompt from .utils import raise_openai_exception @@ -27,8 +27,8 @@ class FamilyModel(Enum): chat = "GPT-3.5" gpt4 = "GPT-4" - gpt4o = "GPT-4o" - gpt4o_mini = "GPT-4o-mini" + gpt4o = "o4-mini" + gpt4o_mini = "o4-mini-mini" instruct_gpt = "InstructGPT" BASE_URL_MAPPING = { @@ -55,9 +55,9 @@ def __post_init__(self): self.family = FamilyModel.instruct_gpt.value elif self.model.startswith("gpt-3"): self.family = FamilyModel.chat.value - elif self.model.startswith("gpt-4o-mini"): + elif self.model.startswith("o4-mini-mini"): self.family = FamilyModel.gpt4o_mini.value - elif self.model.startswith("gpt-4o"): + elif self.model.startswith("o4-mini"): self.family = FamilyModel.gpt4o.value elif self.model.startswith(("gpt4", "gpt-4", "gpt")): self.family = FamilyModel.gpt4.value @@ -98,12 +98,11 @@ def get_client(self, client_secrets: dict = {}): def streaming( self, client: OpenAI, - messages: t.List[dict], + stream_response: StreamingResponse, max_tokens: int, stream_function: t.Callable, check_connection: t.Callable, stream_params: dict, - stream_response: StreamingResponse, **kwargs ) -> StreamingResponse: """Process streaming response from OpenAI.""" @@ -112,18 +111,21 @@ def streaming( try: call_kwargs = { - "messages": messages, - "max_tokens": max_tokens, + "messages": stream_response.messages, "stream": True, **self.get_params(), **kwargs } + if max_tokens: + call_kwargs["max_completion_tokens"] = min(max_tokens, self.max_sample_budget) for part in client.chat.completions.create(**call_kwargs): if not part.choices: continue delta = part.choices[0].delta + if part.choices and 'finish_reason' in part.choices[0]: + stream_response.finish_reason = part.choices[0].finish_reason if not delta: continue @@ -143,7 +145,7 @@ def streaming( if tool_call_started and "" in content: stream_response.is_detected_tool_call = True - stream_response.detected_tool_call = content + stream_response.content = content break stream_response.content = content @@ -151,5 +153,6 @@ def streaming( except Exception as e: stream_response.content = content + stream_response.finish_reason = FINISH_REASON_ERROR logger.exception("Exception during stream processing", exc_info=e) raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e \ No newline at end of file diff --git a/lamoom/ai_models/openai/responses.py b/lamoom/ai_models/openai/responses.py index 760f4cc..8d29bcf 100644 --- a/lamoom/ai_models/openai/responses.py +++ b/lamoom/ai_models/openai/responses.py @@ -6,13 +6,9 @@ from openai.types.chat import ChatCompletionMessage as Message from openai.types.chat import ChatCompletionMessageToolCall as ToolCall -from lamoom.responses import AIResponse +from lamoom.responses import FINISH_REASON_LENGTH, FINISH_REASON_TOOL_CALLS, AIResponse from lamoom.ai_models.tools.base_tool import ToolDefinition -FINISH_REASON_LENGTH = "length" -FINISH_REASON_ERROR = "error" -FINISH_REASON_FINISH = "stop" -FINISH_REASON_TOOL_CALLS = "tool_calls" logger = logging.getLogger(__name__) @@ -20,19 +16,11 @@ @dataclass(kw_only=True) class StreamingResponse(AIResponse): is_detected_tool_call: bool = False - detected_tool_call: t.Optional[dict] = None tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) messages: t.List[dict] = field(default_factory=list) def add_message(self, role: str, content: str): self.messages.append({"role": role, "content": content}) - - def add_tool_result(self, tool_name: str, result: str): - self.messages.append({ - "role": "tool", - "name": tool_name, - "content": result - }) @dataclass(kw_only=True) diff --git a/lamoom/ai_models/tools/base_tool.py b/lamoom/ai_models/tools/base_tool.py index 1e07af0..8bd19d5 100644 --- a/lamoom/ai_models/tools/base_tool.py +++ b/lamoom/ai_models/tools/base_tool.py @@ -100,15 +100,11 @@ def parse_tool_call_block(text_response: str) -> t.Optional[ToolCallResult]: # Regex to find the block, allowing for whitespace variations # DOTALL allows '.' to match newlines within the JSON block - match = re.search( - rf"{re.escape(TOOL_CALL_START_TAG)}(.*?){re.escape(TOOL_CALL_END_TAG)}", - text_response, - re.DOTALL | re.IGNORECASE - ) + json_content = text_response[text_response.find( + TOOL_CALL_START_TAG) + len(TOOL_CALL_START_TAG): text_response.rfind(TOOL_CALL_END_TAG)] + if '```' in json_content.split('\n'): + json_content = '\n'.join(json_content.split('\n')[1:-1]) - if not match: - return None - json_content = match.group(1).strip() logger.debug(f"Found potential tool call JSON block: {json_content}") try: @@ -188,11 +184,9 @@ def handle_tool_call(current_stream_part_content, tool_registry) -> ToolCallResu ) -def format_tool_result_message(tool_name, tool_result_str): +def format_tool_result_message(tool_result: ToolCallResult): return f''' -{{ -"tool_name": "{tool_name}", -"parameters": "{tool_result_str}" -}} + +{tool_result.execution_result} ''' \ No newline at end of file diff --git a/lamoom/responses.py b/lamoom/responses.py index 5f90591..05a206d 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -3,9 +3,15 @@ import logging from dataclasses import dataclass, field import typing as t +from lamoom.ai_models.tools.base_tool import ToolCallResult, ToolDefinition, format_tool_result_message logger = logging.getLogger(__name__) +FINISH_REASON_LENGTH = "length" +FINISH_REASON_ERROR = "error" +FINISH_REASON_FINISH = "stop" +FINISH_REASON_TOOL_CALLS = "tool_calls" + @dataclass class Prompt: @@ -41,3 +47,65 @@ def response(self) -> str: def get_message_str(self) -> str: return self.response + + +@dataclass(kw_only=True) +class StreamingResponse(AIResponse): + is_detected_tool_call: bool = False + last_detected_tool_call: t.Optional[dict] = None + detected_tool_calls: list[t.Optional[dict]] = field(default_factory=list) + tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) + messages: t.List[dict] = field(default_factory=list) + + def add_message(self, role: str, content: str): + self.messages.append({"role": role, "content": content}) + + def add_tool_result(self, tool_result: ToolCallResult): + logger.info(f'TOOL_CALL: Added tool results {tool_result}') + self.content +=f'\n{json.dumps(tool_result.tool_name)}\n' + self.content += format_tool_result_message(tool_result) + self.add_message("assistant", self.content) + + + @property + def response(self) -> str: + return self.content + + @property + def tool_calls(self) -> t.List[t.Optional[dict]]: + return self.detected_tool_calls + + def get_function_name(self, tool_call: t.Optional[dict]) -> t.Optional[str]: + if tool_call.type != "function": + logger.error(f"function.type is not function: {tool_call.type}") + return None + return tool_call.function.name + + def get_function_args(self, tool_call: t.Optional[dict]) -> t.Dict[str, t.Any]: + if not self.is_function() or not tool_call.function: + return {} + arguments = tool_call.function.arguments + try: + return json.loads(arguments) + except json.JSONDecodeError as e: + logger.debug("Failed to parse function arguments", exc_info=e) + return {} + + def is_reached_limit(self) -> bool: + return self.finish_reason == FINISH_REASON_LENGTH + + def to_dict(self) -> t.Dict[str, str]: + return { + "finish_reason": self.finish_reason, + "message": json.dumps(self.messages, indent=2) + } + + def get_message_str(self) -> str: + return json.dumps(self.messages, indent=2) + + def __str__(self) -> str: + result = ( + f"finish_reason: {self.finish_reason}\n" + f"message: {self.get_message_str()}\n" + ) + return result \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 6d0ac7a..9737490 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,71 +25,6 @@ def lamoom(): azure_keys={"us-east-1": {"url": "https://us-east-1.api.azure.openai.org", "key": "123"}} ) -@pytest.fixture -def openai_gpt_4_behaviour(): - return behaviour.AIModelsBehaviour( - attempts=[ - AttemptToCall( - ai_model=OpenAIModel( - model="gpt-4-1106-preview", - max_tokens=C_128K, - support_functions=True, - ), - weight=100, - ), - ] - ) - - -@pytest.fixture -def azure_gpt_4_behaviour(): - return behaviour.AIModelsBehaviour( - attempts=[ - AttemptToCall( - ai_model=AzureAIModel( - realm='useast', - deployment_id="gpt-4o", - max_tokens=C_128K, - support_functions=True, - ), - weight=100, - ), - ] - ) - - -@pytest.fixture -def gpt_4_behaviour(): - return behaviour.AIModelsBehaviour( - attempts=[ - AttemptToCall( - ai_model=AzureAIModel( - realm='useast', - deployment_id='gpt-4o', - max_tokens=C_128K, - ), - weight=1, - ), - AttemptToCall( - ai_model=OpenAIModel( - model="gpt-4-1106-preview", - max_tokens=C_128K, - support_functions=True, - ), - weight=100, - ), - ], - fallback_attempt=AttemptToCall( - ai_model=AzureAIModel( - realm="useast", - deployment_id="gpt-4o", - max_tokens=C_32K, - support_functions=True, - ), - weight=1, - ), - ) - @pytest.fixture def hello_world_prompt(): prompt = Prompt(id='hello-world') diff --git a/tests/prompts/test_model.py b/tests/prompts/test_model.py index b22efa6..28a42a4 100644 --- a/tests/prompts/test_model.py +++ b/tests/prompts/test_model.py @@ -29,7 +29,7 @@ def test_model(client): result = client.call(prompt.id, context, "custom/deepseek-ai/DeepSeek-R1", provider_url="https://api.studio.nebius.ai/v1/") assert result.content - result = client.call(prompt.id, context, "openai/gpt-4o") + result = client.call(prompt.id, context, "openai/o4-mini") assert result.content result = client.call(prompt.id, context, "azure/useast/gpt-4o") diff --git a/tests/prompts/test_pricing.py b/tests/prompts/test_pricing.py index f2c9b86..3d77c6f 100644 --- a/tests/prompts/test_pricing.py +++ b/tests/prompts/test_pricing.py @@ -36,11 +36,11 @@ def test_openai_pricing(lamoom_client: Lamoom): prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') - result_4o = lamoom_client.call(prompt.id, context, "openai/gpt-4o", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - result_4o_mini = lamoom_client.call(prompt.id, context, "openai/gpt-4o-mini", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + result_4o = lamoom_client.call(prompt.id, context, "openai/o4-mini", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + result_4o_mini = lamoom_client.call(prompt.id, context, "openai/o4-mini-mini", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - assert result_4o.metrics.price_of_call > 0 - assert result_4o_mini.metrics.price_of_call > 0 + assert result_4o.metrics.price_of_call >= 0 + assert result_4o_mini.metrics.price_of_call >= 0 def test_claude_pricing(lamoom_client: Lamoom): @@ -51,7 +51,7 @@ def test_claude_pricing(lamoom_client: Lamoom): } # initial version of the prompt - prompt_id = f'test-{time.time()}' + prompt_id = f'test' lamoom_client.service.clear_cache() prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') diff --git a/tests/prompts/test_prompt.py b/tests/prompts/test_prompt.py index dce1ce4..49d8187 100644 --- a/tests/prompts/test_prompt.py +++ b/tests/prompts/test_prompt.py @@ -15,7 +15,7 @@ def azure_ai_attempt(): return AttemptToCall( ai_model=AzureAIModel( realm='useast', - deployment_id="gpt-4o", + deployment_id="o4-mini", max_tokens=C_128K, support_functions=True, ), diff --git a/tests/prompts/test_stream.py b/tests/prompts/test_stream.py index 8cfab56..642cc23 100644 --- a/tests/prompts/test_stream.py +++ b/tests/prompts/test_stream.py @@ -2,6 +2,7 @@ import logging import time +from lamoom.responses import StreamingResponse from pytest import fixture from lamoom import Lamoom, Prompt, OpenAIResponse logger = logging.getLogger(__name__) @@ -36,7 +37,7 @@ def test_stream(client: Lamoom): prompt.add("{text}") prompt.add("It's a system message, Hello {name}", role="assistant") - result: OpenAIResponse = client.call(prompt.id, context, "azure/useast/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + result: StreamingResponse = client.call(prompt.id, context, "azure/useast/gpt-4o", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) client.call(prompt.id, context, "claude/claude-3-haiku-20240307", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) client.call(prompt.id, context, "gemini/gemini-1.5-flash", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 0a11b56..2b73031 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -34,13 +34,15 @@ def test_web_call(client): prompt.add("{text}", role='user') prompt.add_tool(WEB_SEARCH_TOOL) - result = client.call(prompt.id, context, "openai/gpt-4o") + result = client.call(prompt.id, context, "openai/o4-mini") + print(result) + assert 0 == 1 result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - # with open('test_web_call.txt', 'w', encoding="utf-8") as f: - # f.write(result.content) + with open('test_web_call.txt', 'w', encoding="utf-8") as f: + f.write(result.content) assert result.content \ No newline at end of file diff --git a/tests/test_integrational.py b/tests/test_integrational.py index 90b8404..e93213c 100644 --- a/tests/test_integrational.py +++ b/tests/test_integrational.py @@ -54,4 +54,4 @@ def test_loading_prompt_from_service(client: Lamoom): result = client.call(prompt.id, context, "azure/useast/gpt-4o") # should call the prompt with music - assert result.prompt.messages[-1] == {'role': 'user', 'content': 'music1\nmusic2'} + assert result.messages[-1] == {'role': 'user', 'content': 'music1\nmusic2'} From 4e3c0479b1fa345775b4068b0ae5813baa6f4d0c Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 7 May 2025 21:26:15 -0700 Subject: [PATCH 12/34] udpates --- lamoom/ai_models/ai_model.py | 4 +-- lamoom/ai_models/openai/openai_models.py | 40 ++++++++++++------------ lamoom/ai_models/tools/base_tool.py | 6 ++-- lamoom/ai_models/tools/web_tool.py | 2 +- lamoom/responses.py | 4 +-- tests/prompts/test_web_call.py | 12 +++++-- 6 files changed, 38 insertions(+), 30 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 47cdce8..3c5a752 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -86,10 +86,10 @@ def call( logger.info(f'executed parsed_tool_call {parsed_tool_call}') stream_response.add_message("assistant", stream_response.content) stream_response.add_tool_result(parsed_tool_call) - logger.info(f'Added message {stream_response.messages}') - # Update messages for next iteration attempts -= 1 + logger.info(f'Left attempts: {attempts}, Added message {stream_response.messages[-1]}') continue + logger.info(f'Passing execution, finished') break except RetryableCustomError: attempts -= 1 diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 59ceac4..ad7d507 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -11,7 +11,7 @@ from lamoom.ai_models.openai.responses import OpenAIResponse, StreamingResponse from lamoom.ai_models.utils import get_common_args from lamoom.exceptions import ConnectionLostError, RetryableCustomError -from lamoom.ai_models.tools.base_tool import ToolDefinition, inject_tool_prompts, parse_tool_call_block +from lamoom.ai_models.tools.base_tool import TOOL_CALL_END_TAG, TOOL_CALL_START_TAG, ToolDefinition, inject_tool_prompts, parse_tool_call_block import json from openai.types.chat import ChatCompletionMessage as Message @@ -118,36 +118,36 @@ def streaming( } if max_tokens: call_kwargs["max_completion_tokens"] = min(max_tokens, self.max_sample_budget) - - for part in client.chat.completions.create(**call_kwargs): + completion = client.chat.completions.create(**call_kwargs) + for part in completion: if not part.choices: continue delta = part.choices[0].delta if part.choices and 'finish_reason' in part.choices[0]: + logger.info(f'Finish reason: {part.choices[0].finish_reason}') stream_response.finish_reason = part.choices[0].finish_reason - if not delta: - continue - - if delta.content: - content += delta.content - if stream_function: - stream_function(delta.content, **stream_params) - - if check_connection and not check_connection(**stream_params): - raise ConnectionLostError("Connection was lost!") - + # Check for tool call markers - if "" in content: - if not tool_call_started: - tool_call_started = True + if TOOL_CALL_START_TAG in content and not tool_call_started: + tool_call_started = True + logger.info(f'tool_call_started: {tool_call_started}') + + if not delta: continue - - if tool_call_started and "" in content: + content += delta.content + if stream_function: + stream_function(delta.content, **stream_params) + + logger.debug(f'{delta.content}') + if tool_call_started and TOOL_CALL_END_TAG in content: + logger.info(f'tool_call_ended: {content}') stream_response.is_detected_tool_call = True stream_response.content = content break - + if check_connection and not check_connection(**stream_params): + raise ConnectionLostError("Connection was lost!") + stream_response.content = content return stream_response diff --git a/lamoom/ai_models/tools/base_tool.py b/lamoom/ai_models/tools/base_tool.py index 8bd19d5..930b516 100644 --- a/lamoom/ai_models/tools/base_tool.py +++ b/lamoom/ai_models/tools/base_tool.py @@ -7,9 +7,11 @@ logger = logging.getLogger(__name__) +TOOL_CALL_NAME = 'tool_call' +TOOL_CALL_RESULT_NAME = 'tool_call_result' # --- Constants for Prompting --- -TOOL_CALL_START_TAG = "" -TOOL_CALL_END_TAG = "" +TOOL_CALL_START_TAG = f"<{TOOL_CALL_NAME}>" +TOOL_CALL_END_TAG = f"" def get_tool_system_prommpt(tool_descriptions: str): diff --git a/lamoom/ai_models/tools/web_tool.py b/lamoom/ai_models/tools/web_tool.py index 861bf7e..343f557 100644 --- a/lamoom/ai_models/tools/web_tool.py +++ b/lamoom/ai_models/tools/web_tool.py @@ -86,7 +86,7 @@ def format_search_results(results: t.List[WebSearchResult]) -> str: formatted += f"URL: {result.url}\n" formatted += f"Snippet: {result.snippet}\n" formatted += f"Content: {result.content[:500]}...\n" - formatted += '' + formatted += f'' logger.debug(f"Adding into LLM context\n:{formatted}") return formatted diff --git a/lamoom/responses.py b/lamoom/responses.py index 05a206d..4052a91 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -3,7 +3,7 @@ import logging from dataclasses import dataclass, field import typing as t -from lamoom.ai_models.tools.base_tool import ToolCallResult, ToolDefinition, format_tool_result_message +from lamoom.ai_models.tools.base_tool import TOOL_CALL_NAME, TOOL_CALL_RESULT_NAME, ToolCallResult, ToolDefinition, format_tool_result_message logger = logging.getLogger(__name__) @@ -62,7 +62,7 @@ def add_message(self, role: str, content: str): def add_tool_result(self, tool_result: ToolCallResult): logger.info(f'TOOL_CALL: Added tool results {tool_result}') - self.content +=f'\n{json.dumps(tool_result.tool_name)}\n' + self.content +=f'\n<{TOOL_CALL_RESULT_NAME}="{tool_result.tool_name}">\n{json.dumps(tool_result.tool_name)}\n\n' self.content += format_tool_result_message(tool_result) self.add_message("assistant", self.content) diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 2b73031..ba836f4 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -1,6 +1,7 @@ import logging import time +from lamoom.ai_models.tools.base_tool import TOOL_CALL_NAME from pytest import fixture from lamoom import Lamoom, Prompt from lamoom.ai_models.tools.web_tool import WEB_SEARCH_TOOL @@ -24,7 +25,7 @@ def stream_check_connection(validate, **kwargs): def test_web_call(client): context = { - 'text': "Summarize the latest reviews for the movie 'Dune: Part Two'." + 'text': f"Summarize the latest reviews for the movie 'Dune: Part Two'. Please use in your answer final <{TOOL_CALL_NAME}>s" } # initial version of the prompt @@ -36,13 +37,18 @@ def test_web_call(client): result = client.call(prompt.id, context, "openai/o4-mini") print(result) + print(result.content) + assert result.content + with open('test_web_call_openai.txt', 'w', encoding="utf-8") as f: + f.write(result.content) + assert 0 == 1 result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - + with open('test_web_call.txt', 'w', encoding="utf-8") as f: f.write(result.content) - + assert result.content \ No newline at end of file From 599b3af2a4a90e112a8d85dbc540db91f39ca61d Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Fri, 9 May 2025 17:10:03 -0700 Subject: [PATCH 13/34] testing w/ deepseek --- README.md | 1 + .../evaluate_prompt_quality.py | 16 +---- lamoom/ai_models/ai_model.py | 22 +++++-- lamoom/ai_models/claude/claude_model.py | 14 ++--- lamoom/ai_models/openai/openai_models.py | 23 +++++--- lamoom/ai_models/tools/base_tool.py | 20 ++++--- lamoom/ai_models/tools/web_tool.py | 4 +- lamoom/prompt/lamoom.py | 58 ++++++++++++++----- lamoom/responses.py | 29 ++++++++-- lamoom/settings.py | 2 + tests/prompts/test_web_call.py | 48 +++++++-------- 11 files changed, 148 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index e85858c..e903f54 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ Mix models easily, and districute the load across models. The system will automa - Gemini - OpenAI (w/ Azure OpenAI models) - Nebius with (Llama, DeepSeek, Mistral, Mixtral, dolphin, Qwen and others) +- OpenRouter woth open source models - Custom providers Model string format is the following for Claude, Gemini, OpenAI, Nebius: diff --git a/docs/evaluate_prompts_quality/evaluate_prompt_quality.py b/docs/evaluate_prompts_quality/evaluate_prompt_quality.py index f775e5d..fedb71c 100644 --- a/docs/evaluate_prompts_quality/evaluate_prompt_quality.py +++ b/docs/evaluate_prompts_quality/evaluate_prompt_quality.py @@ -9,20 +9,6 @@ lamoom = Lamoom() -gpt4_behaviour = behaviour.AIModelsBehaviour( - attempts=[ - AttemptToCall( - ai_model=AzureAIModel( - realm='useast', - deployment_id="o4-mini", - max_tokens=C_128K, - support_functions=True, - ), - weight=100, - ), - ] -) - def main(): for prompt in get_all_prompts(): @@ -41,7 +27,7 @@ def main(): 'prompt_data': prompt_chats, 'prompt_id': prompt_id, } - result = lamoom.call(prompt_to_evaluate_prompt.id, context, gpt4_behaviour) + result = lamoom.call(prompt_to_evaluate_prompt.id, context, 'azure/useast/o4-mini') print(result.content) if __name__ == '__main__': diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 3c5a752..2b0baa8 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,3 +1,4 @@ +import datetime import json import typing as t from dataclasses import dataclass @@ -18,10 +19,12 @@ class AI_MODELS_PROVIDER(Enum): GEMINI = "gemini" NEBIUS = "nebius" CUSTOM = "custom" + OPENROUTER = "openrouter" @dataclass(kw_only=True) class AIModel: + model: t.Optional[str] = '' tiktoken_encoding: t.Optional[str] = "cl100k_base" provider: AI_MODELS_PROVIDER = None support_functions: bool = False @@ -44,16 +47,19 @@ def call( messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], tool_registry: t.Dict[str, ToolDefinition] = {}, - max_tool_iterations: int = 5, # Safety limit for sequential calls + max_tool_iterations: int = 3, # Safety limit for sequential calls stream_function: t.Callable = None, check_connection: t.Callable = None, stream_params: dict = {}, client_secrets: dict = {}, + modelname='', **kwargs, ) -> AIResponse: """Common call implementation that handles streaming and tool calls.""" client = self.get_client(client_secrets) + logger.debug(f"[call] messages, tool_registry: {tool_registry}") tool_definitions = list(tool_registry.values()) + logger.debug(f"[call] tool_definitions: {tool_definitions}") # Inject tool prompts into first message current_messages = inject_tool_prompts(messages, tool_definitions) # Prepare streaming response @@ -61,6 +67,7 @@ def call( tool_registry=tool_registry, messages=current_messages ) + modelname = modelname.replace('/', '_').replace('-', '_') attempts = max_tool_iterations while attempts > 0: try: @@ -84,14 +91,19 @@ def call( self.handle_tool_call(parsed_tool_call, tool_registry) # Add messages to history logger.info(f'executed parsed_tool_call {parsed_tool_call}') - stream_response.add_message("assistant", stream_response.content) stream_response.add_tool_result(parsed_tool_call) - attempts -= 1 + with open(f'test_web_call_{modelname}_{attempts}.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(stream_response.messages, indent=2)) logger.info(f'Left attempts: {attempts}, Added message {stream_response.messages[-1]}') + attempts -= 1 continue - logger.info(f'Passing execution, finished') + stream_response.add_assistant_message() + with open(f'test_web_call_{modelname}_{attempts}.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(stream_response.messages, indent=2)) + logger.info(f'Passing execution, finished. {attempts}') break - except RetryableCustomError: + except RetryableCustomError as e: + logger.exception(f'RetryableCustomError {e}') attempts -= 1 continue return stream_response diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index 67431a1..f4ebefc 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -23,7 +23,6 @@ class FamilyModel(Enum): @dataclass(kw_only=True) class ClaudeAIModel(AIModel): - model: str max_tokens: int = C_4K api_key: str = None provider: AI_MODELS_PROVIDER = AI_MODELS_PROVIDER.CLAUDE @@ -50,8 +49,6 @@ def unify_messages_with_same_role(self, messages: t.List[dict]) -> t.List[dict]: result = [] last_role = None for message in messages: - if message.get("role") == "system": - message["role"] = "user" if last_role != message.get("role"): result.append(message) last_role = message.get("role") @@ -72,19 +69,16 @@ def streaming( """Process streaming response from Claude.""" tool_call_started = False content = "" - stream_response.finish_reason = FINISH_REASON_FINISH try: - # Prepare messages for Claude - unified_messages = self.unify_messages_with_same_role(stream_response.messages) + unified_messages = self.unify_messages_with_same_role(stream_response.messages) call_kwargs = { "model": self.model, "max_tokens": max_tokens, "messages": unified_messages, **kwargs } - # Extract system prompt if present system_prompt = [] for i, msg in enumerate(unified_messages): @@ -98,7 +92,8 @@ def streaming( for text_chunk in stream.text_stream: if check_connection and not check_connection(**stream_params): raise ConnectionLostError("Connection was lost!") - + + stream_response.set_streaming() content += text_chunk if stream_function: stream_function(text_chunk, **stream_params) @@ -114,11 +109,12 @@ def streaming( tool_call_started = True continue stream_response.content = content + stream_response.set_finish_reason(FINISH_REASON_FINISH) return stream_response except Exception as e: stream_response.content = content - stream_response.finish_reason = FINISH_REASON_ERROR + stream_response.set_finish_reason(FINISH_REASON_ERROR) logger.exception("Exception during stream processing", exc_info=e) raise RetryableCustomError(f"Claude AI stream processing failed: {e}") from e diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index ad7d507..d4af484 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -31,15 +31,16 @@ class FamilyModel(Enum): gpt4o_mini = "o4-mini-mini" instruct_gpt = "InstructGPT" + BASE_URL_MAPPING = { 'gemini': "https://generativelanguage.googleapis.com/v1beta/openai/", - 'nebius': 'https://api.studio.nebius.ai/v1/' + 'nebius': 'https://api.studio.nebius.ai/v1/', + 'openrouter': 'https://openrouter.ai/api/v1', } @dataclass(kw_only=True) class OpenAIModel(AIModel): - model: t.Optional[str] max_tokens: int = C_16K support_functions: bool = False provider: AI_MODELS_PROVIDER = AI_MODELS_PROVIDER.OPENAI @@ -80,6 +81,10 @@ def get_params(self) -> t.Dict[str, t.Any]: def get_base_url(self) -> str | None: return BASE_URL_MAPPING.get(self.provider.value, None) + def is_provider_openai(self): + return self.provider == AI_MODELS_PROVIDER.OPENAI + + def get_metrics_data(self): return { "model": self.model, @@ -108,7 +113,7 @@ def streaming( """Process streaming response from OpenAI.""" tool_call_started = False content = "" - + try: call_kwargs = { "messages": stream_response.messages, @@ -126,20 +131,22 @@ def streaming( delta = part.choices[0].delta if part.choices and 'finish_reason' in part.choices[0]: logger.info(f'Finish reason: {part.choices[0].finish_reason}') - stream_response.finish_reason = part.choices[0].finish_reason + stream_response.set_finish_reason(part.choices[0].finish_reason) # Check for tool call markers if TOOL_CALL_START_TAG in content and not tool_call_started: tool_call_started = True logger.info(f'tool_call_started: {tool_call_started}') - if not delta: + if not delta or (not delta.content and getattr(delta, 'reasoning', None)): continue content += delta.content + # logger.debug(f'Adding content {delta.content}') + if getattr(delta, 'reasoning', None) and delta.reasoning: + logger.debug(f'Adding reasoning {delta.reasoning}') + stream_response.reasoning += delta.reasoning if stream_function: stream_function(delta.content, **stream_params) - - logger.debug(f'{delta.content}') if tool_call_started and TOOL_CALL_END_TAG in content: logger.info(f'tool_call_ended: {content}') stream_response.is_detected_tool_call = True @@ -153,6 +160,6 @@ def streaming( except Exception as e: stream_response.content = content - stream_response.finish_reason = FINISH_REASON_ERROR + stream_response.set_finish_reason(FINISH_REASON_ERROR) logger.exception("Exception during stream processing", exc_info=e) raise RetryableCustomError(f"OpenAI stream processing failed: {e}") from e \ No newline at end of file diff --git a/lamoom/ai_models/tools/base_tool.py b/lamoom/ai_models/tools/base_tool.py index 930b516..c8df417 100644 --- a/lamoom/ai_models/tools/base_tool.py +++ b/lamoom/ai_models/tools/base_tool.py @@ -15,12 +15,18 @@ def get_tool_system_prommpt(tool_descriptions: str): - return f"""You have next skills: + return f"""You have next tools: ``` {tool_descriptions} ``` +# Tool calling procedure -If you wish to make a call you need to make a call: +Before calling any tool, please follow procedure. You're doing unnecessary calls of tools. Make a mindset of what you need to do. +## 1. Think out loud what you need to do +## 2. Provide 5 whys; +## 3. Call a tool; + +If you need to use a tool, use the next format: ``` """ + TOOL_CALL_START_TAG + """ { @@ -70,6 +76,7 @@ def inject_tool_prompts( ) -> t.List[dict]: """Injects tool descriptions and usage instructions into the system prompt.""" if not available_tools: + logger.debug("[inject_tool_prompts] No tools available. Returning original messages.") return messages tool_descriptions = "\n".join([format_tool_description(tool) for tool in available_tools]) @@ -81,6 +88,7 @@ def inject_tool_prompts( if msg.get("role") == "system": # Append to existing system prompt modified_messages[i]["content"] = f"{msg.get('content', '')}\n\n{tool_system_prompt}" + logger.debug(f"[inject_tool_prompts] Injected tool system prompt:\n{modified_messages[i]['content']}") found_system = True break @@ -88,7 +96,7 @@ def inject_tool_prompts( # Prepend a new system message modified_messages.insert(0, {"role": "system", "content": tool_system_prompt}) - logger.debug(f"Injected tool system prompt:\n{tool_system_prompt}") + logger.debug(f"[inject_tool_prompts] Msg not found. Injected tool system prompt as a first msg :\n{tool_system_prompt}") return modified_messages @@ -187,8 +195,4 @@ def handle_tool_call(current_stream_part_content, tool_registry) -> ToolCallResu def format_tool_result_message(tool_result: ToolCallResult): - return f''' - -{tool_result.execution_result} - -''' \ No newline at end of file + return f'\n<{TOOL_CALL_RESULT_NAME}="{tool_result.tool_name}">\n{json.dumps(tool_result.execution_result)}\n\n## Please, Analyze tool_call_result! Answer next using the provided response from the user' \ No newline at end of file diff --git a/lamoom/ai_models/tools/web_tool.py b/lamoom/ai_models/tools/web_tool.py index 343f557..d8df6c7 100644 --- a/lamoom/ai_models/tools/web_tool.py +++ b/lamoom/ai_models/tools/web_tool.py @@ -54,7 +54,7 @@ def perform_web_search(query: str) -> t.List[WebSearchResult]: 'q': query, 'key': API_KEY, 'cx': SEARCH_ID, - 'num': 3 + 'num': 2 } response = requests.get(url, params=params) @@ -107,7 +107,7 @@ def perform_web_search(query: str) -> str: WEB_SEARCH_TOOL = ToolDefinition( name="web_call", - description="Performs a web search using a search engine to find up-to-date information or details not present in the internal knowledge.", + description="Performs a web search using a search engine to find up-to-date information or details not present in the internal knowledge. Today is {current_datetime_strftime} {timezone}.", parameters=[ ToolParameter(name="query", type="string", description="The search query to use.", required=True) ], diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 9f27bc4..4e3b173 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -1,3 +1,4 @@ +from datetime import datetime import logging import typing as t from dataclasses import dataclass @@ -41,6 +42,7 @@ class Lamoom: azure_keys: t.Dict[str, str] = None nebius_key: str = None custom_key: str = None + openrouter_key: str = None secrets: Secrets = None clients = {} @@ -71,6 +73,9 @@ def __post_init__(self): if not self.nebius_key and self.secrets.NEBIUS_API_KEY: logger.debug(f"Using Nebius API key from secrets") self.nebius_key = self.secrets.NEBIUS_API_KEY + if not self.openrouter_key and self.secrets.OPENROUTER_KEY: + logger.debug(f"Using OpenRouter API key from secrets") + self.openrouter_key = self.secrets.OPENROUTER_KEY if not self.custom_key and self.secrets.CUSTOM_API_KEY: logger.debug(f"Using Custom API key from secrets") self.custom_key = self.secrets.CUSTOM_API_KEY @@ -96,6 +101,8 @@ def __post_init__(self): self.clients[AI_MODELS_PROVIDER.GEMINI] = {"api_key": self.gemini_key} if self.nebius_key: self.clients[AI_MODELS_PROVIDER.NEBIUS] = {"api_key": self.nebius_key} + if self.openrouter_key: + self.clients[AI_MODELS_PROVIDER.OPENROUTER] = {"api_key": self.openrouter_key} if self.custom_key: self.clients[AI_MODELS_PROVIDER.CUSTOM] = {"api_key": self.custom_key} self.worker = SaveWorker() @@ -146,6 +153,15 @@ def extract_provider_name(self, model: str, provider_url: str = None) -> dict: 'realm': None, 'base_url': None } + elif "openrouter" in parts[0].lower() and len(parts) == 3: + model_provider = parts[0] + model_name = f"{parts[1]}/{parts[2]}" + return { + 'provider': model_provider.lower(), + 'model_name': model_name, + 'realm': None, + 'base_url': None + } elif "custom" in parts[0].lower(): if len(parts) == 3: model_provider = parts[0] @@ -166,22 +182,24 @@ def extract_provider_name(self, model: str, provider_url: str = None) -> dict: 'realm': None, 'base_url': None } - + + def get_default_context(self): + return { + 'current_datetime_strftime': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + 'timezone': datetime.now().astimezone().tzname() + } + + def get_context(self, context: dict): + return { + **self.get_default_context(), + **context + } + def init_attempt(self, model_info: dict) -> AttemptToCall: provider = model_info['provider'] model_name = model_info['model_name'] - if provider in [AI_MODELS_PROVIDER.OPENAI.value, - AI_MODELS_PROVIDER.GEMINI.value, - AI_MODELS_PROVIDER.NEBIUS.value]: - return AttemptToCall( - ai_model=OpenAIModel( - provider=AI_MODELS_PROVIDER(provider), - model=model_name, - ), - weight=100, - ) - elif provider == AI_MODELS_PROVIDER.CLAUDE.value: + if provider == AI_MODELS_PROVIDER.CLAUDE.value: return AttemptToCall( ai_model=ClaudeAIModel( model=model_name, @@ -197,7 +215,7 @@ def init_attempt(self, model_info: dict) -> AttemptToCall: ), weight=100, ) - else: + elif provider == AI_MODELS_PROVIDER.AZURE.value: return AttemptToCall( ai_model=AzureAIModel( realm=model_info['realm'], @@ -205,6 +223,14 @@ def init_attempt(self, model_info: dict) -> AttemptToCall: ), weight=100, ) + else: + return AttemptToCall( + ai_model=OpenAIModel( + provider=AI_MODELS_PROVIDER(provider), + model=model_name, + ), + weight=100, + ) def init_behavior(self, model: str, provider_url: str = None) -> AIModelsBehaviour: main_model_info = self.extract_provider_name(model, provider_url) @@ -252,7 +278,8 @@ def call( while prompt_attempts.initialize_attempt(): current_attempt = prompt_attempts.current_attempt user_prompt = prompt.create_prompt(current_attempt) - calling_messages = user_prompt.resolve(context) + calling_context = self.get_context(context) + calling_messages = user_prompt.resolve(calling_context) for _ in range(0, count_of_retries): try: @@ -264,6 +291,7 @@ def call( check_connection=check_connection, stream_params=stream_params, client_secrets=self.clients[current_attempt.ai_model.provider], + modelname=model, **params, ) @@ -294,7 +322,7 @@ def call( self.worker.add_task( self.api_token, prompt.service_dump(), - context, + calling_context, result, {**test_data, "call_model": model} ) diff --git a/lamoom/responses.py b/lamoom/responses.py index 4052a91..5af81d6 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -2,8 +2,11 @@ import json import logging from dataclasses import dataclass, field +import time import typing as t from lamoom.ai_models.tools.base_tool import TOOL_CALL_NAME, TOOL_CALL_RESULT_NAME, ToolCallResult, ToolDefinition, format_tool_result_message +from lamoom.settings import SHOULD_INCLUDE_REASONING +from lamoom.utils import current_timestamp_ms logger = logging.getLogger(__name__) @@ -36,6 +39,7 @@ class AIResponse: _response: str = "" original_result: object = None content: str = "" + reasoning = '' finish_reason: str = "" prompt: Prompt = field(default_factory=Prompt) metrics: Metrics = field(default_factory=Metrics) @@ -56,17 +60,34 @@ class StreamingResponse(AIResponse): detected_tool_calls: list[t.Optional[dict]] = field(default_factory=list) tool_registry: t.Dict[str, ToolDefinition] = field(default_factory=dict) messages: t.List[dict] = field(default_factory=list) + started_tmst: int = field(default_factory=current_timestamp_ms) + first_stream_tmst: int = None + finished_tmst: int = None + + def set_streaming(self): + if not self.started_tmst: + self.started_tmst = current_timestamp_ms() + + def set_finish_reason(self, reason: str): + self.finished_tmst = current_timestamp_ms() + self.finish_reason = reason + def add_assistant_message(self): + if SHOULD_INCLUDE_REASONING: + self.add_message("assistant", self.reasoning + '\n' + self.content) + else: + self.add_message("assistant", self.content) + def add_message(self, role: str, content: str): self.messages.append({"role": role, "content": content}) def add_tool_result(self, tool_result: ToolCallResult): logger.info(f'TOOL_CALL: Added tool results {tool_result}') - self.content +=f'\n<{TOOL_CALL_RESULT_NAME}="{tool_result.tool_name}">\n{json.dumps(tool_result.tool_name)}\n\n' - self.content += format_tool_result_message(tool_result) - self.add_message("assistant", self.content) + self.add_assistant_message() + tool_result_message = format_tool_result_message(tool_result) + self.content += tool_result_message + self.add_message("user", tool_result_message) - @property def response(self) -> str: return self.content diff --git a/lamoom/settings.py b/lamoom/settings.py index 20baa99..1954188 100644 --- a/lamoom/settings.py +++ b/lamoom/settings.py @@ -34,6 +34,7 @@ RECEIVE_PROMPT_FROM_SERVER = parse_bool( os.environ.get("LAMOOM_RECEIVE_PROMPT_FROM_SERVER", True) ) +SHOULD_INCLUDE_REASONING = parse_bool(os.environ.get("SHOULD_INCLUDE_REASONING", True)) PIPE_PROMPTS = {} FALLBACK_MODELS = [] @@ -45,6 +46,7 @@ class Secrets: CLAUDE_API_KEY: str = field(default_factory=lambda: os.getenv("CLAUDE_API_KEY")) GEMINI_API_KEY: str = field(default_factory=lambda: os.getenv("GEMINI_API_KEY")) NEBIUS_API_KEY: str = field(default_factory=lambda: os.getenv("NEBIUS_API_KEY")) + OPENROUTER_KEY: str = field(default_factory=lambda: os.getenv("OPENROUTER_KEY")) CUSTOM_API_KEY: str = field(default_factory=lambda: os.getenv("CUSTOM_API_KEY")) OPENAI_ORG: str = field(default_factory=lambda: os.getenv("OPENAI_ORG")) azure_keys: dict = field( diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index ba836f4..4ba3308 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -1,10 +1,9 @@ +import json import logging -import time -from lamoom.ai_models.tools.base_tool import TOOL_CALL_NAME +from lamoom.ai_models.tools.web_tool import WEB_SEARCH_TOOL from pytest import fixture from lamoom import Lamoom, Prompt -from lamoom.ai_models.tools.web_tool import WEB_SEARCH_TOOL logger = logging.getLogger(__name__) @@ -25,30 +24,33 @@ def stream_check_connection(validate, **kwargs): def test_web_call(client): context = { - 'text': f"Summarize the latest reviews for the movie 'Dune: Part Two'. Please use in your answer final <{TOOL_CALL_NAME}>s" + 'text': f"What's the latest news in the San Francisco and AI?" } # initial version of the prompt prompt_id = 'test-web-search' - client.service.clear_cache() prompt = Prompt(id=prompt_id) - prompt.add("{text}", role='user') - prompt.add_tool(WEB_SEARCH_TOOL) + prompt.add("{text}", role='system') - result = client.call(prompt.id, context, "openai/o4-mini") - print(result) - print(result.content) + prompt.add_tool(WEB_SEARCH_TOOL) + print(f'prompt.tool_registry: {prompt.tool_registry}') + client.api_token = None + # result = client.call(prompt.id, context, "openai/o4-mini") + # with open('test_web_call_openai_o4.txt', 'w', encoding="utf-8") as f: + # f.write(json.dumps(result.messages, indent=4 )) + # assert result.content + + # result = client.call(prompt.id, context, "claude/claude-3-7-sonnet-latest") + # with open('test_web_call_claude_3_7.txt', 'w', encoding="utf-8") as f: + # f.write(json.dumps(result.messages, indent=4)) + # assert result.content + # result = client.call(prompt.id, context, "openrouter/tngtech/deepseek-r1t-chimera:free", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + # assert result.content + # with open('test_web_call_openrouter_deepseek_r1.txt', 'w', encoding="utf-8") as f: + # f.write(json.dumps(result.messages, indent=4)) + + result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1") assert result.content - with open('test_web_call_openai.txt', 'w', encoding="utf-8") as f: - f.write(result.content) - - assert 0 == 1 - - result = client.call(prompt.id, context, "claude/claude-3-5-sonnet-20240620") - - result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - - with open('test_web_call.txt', 'w', encoding="utf-8") as f: - f.write(result.content) - - assert result.content \ No newline at end of file + with open('test_web_call_nebius_deepseek_r1.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(result.messages, indent=4)) + assert 1 == 2 \ No newline at end of file From 95ad3118a19ca464f9a1224a6317c909b4e638a1 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Fri, 9 May 2025 19:29:10 -0700 Subject: [PATCH 14/34] testing w/ deepseek --- lamoom/ai_models/ai_model.py | 68 +++++++++++++++------ lamoom/ai_models/behaviour.py | 4 -- lamoom/ai_models/openai/openai_models.py | 15 ++--- lamoom/ai_models/tools/base_tool.py | 11 ++-- lamoom/ai_models/tools/web_tool.py | 3 +- lamoom/prompt/lamoom.py | 76 ++++-------------------- lamoom/prompt/prompt.py | 3 - lamoom/prompt/user_prompt.py | 3 +- lamoom/settings.py | 1 + tests/prompts/test_web_call.py | 14 ++--- 10 files changed, 84 insertions(+), 114 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 2b0baa8..9a8912a 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,4 +1,4 @@ -import datetime + import json import typing as t from dataclasses import dataclass @@ -6,9 +6,13 @@ import logging from _decimal import Decimal -from lamoom.ai_models.tools.base_tool import ToolCallResult, ToolDefinition, inject_tool_prompts, parse_tool_call_block +import tiktoken + +from lamoom import settings +from lamoom.ai_models.tools.base_tool import ToolCallResult, ToolDefinition, parse_tool_call_block from lamoom.responses import AIResponse, StreamingResponse from lamoom.exceptions import RetryableCustomError +from lamoom.utils import current_timestamp_ms logger = logging.getLogger(__name__) @@ -22,6 +26,9 @@ class AI_MODELS_PROVIDER(Enum): OPENROUTER = "openrouter" +encoding = tiktoken.get_encoding("cl100k_base") + + @dataclass(kw_only=True) class AIModel: model: t.Optional[str] = '' @@ -44,7 +51,7 @@ def get_metrics_data(self): def call( self, - messages: t.List[t.Dict[str, str]], + current_messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], tool_registry: t.Dict[str, ToolDefinition] = {}, max_tool_iterations: int = 3, # Safety limit for sequential calls @@ -53,15 +60,14 @@ def call( stream_params: dict = {}, client_secrets: dict = {}, modelname='', + prompt: 'Prompt' = None, + context: str = '', + test_data: dict = {}, + client: t.Any = None, **kwargs, ) -> AIResponse: """Common call implementation that handles streaming and tool calls.""" - client = self.get_client(client_secrets) - logger.debug(f"[call] messages, tool_registry: {tool_registry}") - tool_definitions = list(tool_registry.values()) - logger.debug(f"[call] tool_definitions: {tool_definitions}") - # Inject tool prompts into first message - current_messages = inject_tool_prompts(messages, tool_definitions) + model_client = self.get_client(client_secrets) # Prepare streaming response stream_response = StreamingResponse( tool_registry=tool_registry, @@ -72,7 +78,7 @@ def call( while attempts > 0: try: stream_response = self.streaming( - client=client, + client=model_client, stream_response=stream_response, max_tokens=max_tokens, stream_function=stream_function, @@ -82,24 +88,22 @@ def call( ) logger.info(f'stream_response: {stream_response}') if stream_response.is_detected_tool_call: - logger.info(f'is_detected_tool_call') parsed_tool_call = parse_tool_call_block(stream_response.content) + logger.info(f'parsed_tool_call {parsed_tool_call}') - if not parsed_tool_call: + if not parsed_tool_call or attempts <= 1: + attempts -= 1 continue # Execute tool call self.handle_tool_call(parsed_tool_call, tool_registry) # Add messages to history logger.info(f'executed parsed_tool_call {parsed_tool_call}') stream_response.add_tool_result(parsed_tool_call) - with open(f'test_web_call_{modelname}_{attempts}.txt', 'w', encoding="utf-8") as f: - f.write(json.dumps(stream_response.messages, indent=2)) - logger.info(f'Left attempts: {attempts}, Added message {stream_response.messages[-1]}') + self.save_call(stream_response, prompt, context, attempt=max_tool_iterations - attempts, client=client) attempts -= 1 continue stream_response.add_assistant_message() - with open(f'test_web_call_{modelname}_{attempts}.txt', 'w', encoding="utf-8") as f: - f.write(json.dumps(stream_response.messages, indent=2)) + self.save_call(stream_response, prompt, context, test_data=test_data, client=client) logger.info(f'Passing execution, finished. {attempts}') break except RetryableCustomError as e: @@ -147,3 +151,33 @@ def streaming( def get_client(self, client_secrets: dict = {}) -> t.Any: """Get the client instance. Must be implemented by subclasses.""" raise NotImplementedError("Subclasses must implement get_client method") + + + def calculate_budget_for_text(self, text: str) -> int: + if not text: + return 0 + return len(encoding.encode(text)) + + def save_call(self, stream_response: StreamingResponse, prompt: "Prompt", context: str, attempt: int=0, test_data: dict = None, client: t.Any = None): + + sample_budget = self.calculate_budget_for_text( + stream_response.get_message_str() + ) + stream_response.metrics.sample_tokens_used = sample_budget + stream_response.metrics.prompt_tokens_used = self.calculate_budget_for_text( + json.dumps(stream_response.messages) + ) + stream_response.metrics.ai_model_details = ( + self.get_metrics_data() + ) + stream_response.metrics.latency = current_timestamp_ms() - stream_response.started_tmst + + if settings.USE_API_SERVICE and client.api_token: + stream_response.id = f"{prompt.id}#{stream_response.started_tmst}" + (f"#{attempt}" if attempt else "") + self.worker.add_task( + client.api_token, + prompt.service_dump(), + context, + stream_response, + {**test_data, "call_model": self.model} + ) diff --git a/lamoom/ai_models/behaviour.py b/lamoom/ai_models/behaviour.py index 8b340d8..986127d 100644 --- a/lamoom/ai_models/behaviour.py +++ b/lamoom/ai_models/behaviour.py @@ -1,11 +1,7 @@ import logging -import random -import typing as t -from copy import copy from dataclasses import dataclass from lamoom.ai_models.attempt_to_call import AttemptToCall -from lamoom.exceptions import BehaviourIsNotDefined logger = logging.getLogger(__name__) diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index d4af484..1ea862f 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -1,23 +1,18 @@ import logging import typing as t -from dataclasses import dataclass, field -from decimal import Decimal +from dataclasses import dataclass from enum import Enum from openai import OpenAI from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER, AIModel from lamoom.ai_models.constants import C_128K, C_16K, C_32K, C_4K -from lamoom.ai_models.openai.responses import OpenAIResponse, StreamingResponse -from lamoom.ai_models.utils import get_common_args +from lamoom.ai_models.openai.responses import StreamingResponse from lamoom.exceptions import ConnectionLostError, RetryableCustomError -from lamoom.ai_models.tools.base_tool import TOOL_CALL_END_TAG, TOOL_CALL_START_TAG, ToolDefinition, inject_tool_prompts, parse_tool_call_block -import json +from lamoom.ai_models.tools.base_tool import TOOL_CALL_END_TAG, TOOL_CALL_START_TAG -from openai.types.chat import ChatCompletionMessage as Message -from lamoom.responses import FINISH_REASON_ERROR, Prompt +from lamoom.responses import FINISH_REASON_ERROR -from .utils import raise_openai_exception M_DAVINCI = "davinci" @@ -141,7 +136,7 @@ def streaming( if not delta or (not delta.content and getattr(delta, 'reasoning', None)): continue content += delta.content - # logger.debug(f'Adding content {delta.content}') + print(f'D {delta}') if getattr(delta, 'reasoning', None) and delta.reasoning: logger.debug(f'Adding reasoning {delta.reasoning}') stream_response.reasoning += delta.reasoning diff --git a/lamoom/ai_models/tools/base_tool.py b/lamoom/ai_models/tools/base_tool.py index c8df417..418b0f4 100644 --- a/lamoom/ai_models/tools/base_tool.py +++ b/lamoom/ai_models/tools/base_tool.py @@ -4,6 +4,8 @@ import json import re +from lamoom.utils import resolve + logger = logging.getLogger(__name__) @@ -14,10 +16,10 @@ TOOL_CALL_END_TAG = f"" -def get_tool_system_prommpt(tool_descriptions: str): +def get_tool_system_prompt(tool_descriptions: str, context: t.Dict[str, str]): return f"""You have next tools: ``` -{tool_descriptions} +{resolve(tool_descriptions, context)} ``` # Tool calling procedure @@ -72,7 +74,8 @@ def format_tool_description(tool: ToolDefinition) -> str: def inject_tool_prompts( messages: t.List[dict], - available_tools: t.List[ToolDefinition] + available_tools: t.List[ToolDefinition], + context: t.Dict[str, str] ) -> t.List[dict]: """Injects tool descriptions and usage instructions into the system prompt.""" if not available_tools: @@ -80,7 +83,7 @@ def inject_tool_prompts( return messages tool_descriptions = "\n".join([format_tool_description(tool) for tool in available_tools]) - tool_system_prompt = get_tool_system_prommpt(tool_descriptions) + tool_system_prompt = get_tool_system_prompt(tool_descriptions, context) # Find system prompt or prepend to user prompt modified_messages = list(messages) # Create a copy found_system = False diff --git a/lamoom/ai_models/tools/web_tool.py b/lamoom/ai_models/tools/web_tool.py index d8df6c7..d5bbcbe 100644 --- a/lamoom/ai_models/tools/web_tool.py +++ b/lamoom/ai_models/tools/web_tool.py @@ -54,7 +54,7 @@ def perform_web_search(query: str) -> t.List[WebSearchResult]: 'q': query, 'key': API_KEY, 'cx': SEARCH_ID, - 'num': 2 + 'num': LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT } response = requests.get(url, params=params) @@ -87,7 +87,6 @@ def format_search_results(results: t.List[WebSearchResult]) -> str: formatted += f"Snippet: {result.snippet}\n" formatted += f"Content: {result.content[:500]}...\n" formatted += f'' - logger.debug(f"Adding into LLM context\n:{formatted}") return formatted @staticmethod diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 4e3b173..2d520ef 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -2,10 +2,8 @@ import logging import typing as t from dataclasses import dataclass -from decimal import Decimal import requests -import time -from lamoom.ai_models.tools.errors import ToolCallError +from lamoom.ai_models.tools.base_tool import inject_tool_prompts from lamoom.settings import LAMOOM_API_URI from lamoom import Secrets, settings from lamoom.ai_models.ai_model import AI_MODELS_PROVIDER @@ -14,7 +12,6 @@ from lamoom.ai_models.openai.azure_models import AzureAIModel from lamoom.ai_models.claude.claude_model import ClaudeAIModel from lamoom.ai_models.openai.openai_models import OpenAIModel -from lamoom.ai_models.constants import C_16K from lamoom.exceptions import ( LamoomPromptIsnotFoundError, @@ -22,11 +19,9 @@ ) from lamoom.services.SaveWorker import SaveWorker from lamoom.prompt.prompt import Prompt -from lamoom.prompt.user_prompt import UserPrompt from lamoom.responses import AIResponse from lamoom.services.lamoom import LamoomService -from lamoom.utils import current_timestamp_ms import json logger = logging.getLogger(__name__) @@ -266,7 +261,6 @@ def call( """ logger.debug(f"Calling {prompt_id}") - start_time = current_timestamp_ms() prompt = self.get_prompt(prompt_id, version) behaviour = self.init_behavior(model, provider_url) @@ -279,12 +273,15 @@ def call( current_attempt = prompt_attempts.current_attempt user_prompt = prompt.create_prompt(current_attempt) calling_context = self.get_context(context) - calling_messages = user_prompt.resolve(calling_context) + # Inject tool prompts into first message + calling_messages = user_prompt.resolve(calling_context, prompt.tool_registry) + messages = calling_messages.get_messages() + messages = inject_tool_prompts(messages, list(prompt.tool_registry.values()), calling_context) for _ in range(0, count_of_retries): try: result = current_attempt.ai_model.call( - calling_messages.get_messages(), + messages, calling_messages.max_sample_budget, tool_registry=prompt.tool_registry, stream_function=stream_function, @@ -292,40 +289,12 @@ def call( stream_params=stream_params, client_secrets=self.clients[current_attempt.ai_model.provider], modelname=model, + prompt=prompt, + context=json.dumps(context), + test_data=test_data, + client=self, **params, ) - - sample_budget = self.calculate_budget_for_text( - user_prompt, result.get_message_str() - ) - - try: - result.metrics.price_of_call = self.get_price( - current_attempt, - sample_budget, - calling_messages.prompt_budget, - ) - except Exception as e: - logger.exception(f"Error while getting price: {e}") - result.metrics.price_of_call = 0 - result.metrics.sample_tokens_used = sample_budget - result.metrics.prompt_tokens_used = calling_messages.prompt_budget - result.metrics.ai_model_details = ( - current_attempt.ai_model.get_metrics_data() - ) - result.metrics.latency = current_timestamp_ms() - start_time - - if settings.USE_API_SERVICE and self.api_token: - timestamp = int(time.time() * 1000) - result.id = f"{prompt_id}#{timestamp}" - - self.worker.add_task( - self.api_token, - prompt.service_dump(), - calling_context, - result, - {**test_data, "call_model": model} - ) return result except RetryableCustomError as e: logger.exception( @@ -394,28 +363,3 @@ def add_ideal_answer( ) return response - - def calculate_budget_for_text(self, user_prompt: UserPrompt, text: str) -> int: - if not text: - return 0 - return len(user_prompt.encoding.encode(text)) - - def get_price( - self, attempt: AttemptToCall, sample_budget: int, prompt_budget: int - ) -> Decimal: - data = { - "provider": attempt.ai_model.provider.value, - "model": attempt.ai_model.name, - "output_tokens": sample_budget, - "input_tokens": prompt_budget, - } - - response = requests.post( - f"{LAMOOM_API_URI}/lib/pricing", - data=json.dumps(data), - ) - - if response.status_code != 200: - return 0 - - return response.json()["price"] \ No newline at end of file diff --git a/lamoom/prompt/prompt.py b/lamoom/prompt/prompt.py index 5858b5c..5d58936 100644 --- a/lamoom/prompt/prompt.py +++ b/lamoom/prompt/prompt.py @@ -2,11 +2,8 @@ import logging from copy import deepcopy -import typing as t - from lamoom import settings from lamoom.ai_models.attempt_to_call import AttemptToCall -from lamoom.ai_models.tools.base_tool import ToolDefinition from lamoom.prompt.base_prompt import BasePrompt from lamoom.prompt.chat import ChatsEntity from lamoom.prompt.user_prompt import UserPrompt diff --git a/lamoom/prompt/user_prompt.py b/lamoom/prompt/user_prompt.py index 5763d9c..4931d69 100644 --- a/lamoom/prompt/user_prompt.py +++ b/lamoom/prompt/user_prompt.py @@ -3,6 +3,7 @@ from collections import defaultdict from dataclasses import dataclass, field +from lamoom.ai_models.tools.base_tool import ToolDefinition, inject_tool_prompts import tiktoken from lamoom import settings @@ -63,7 +64,7 @@ class UserPrompt(BasePrompt): def __post_init__(self): self.encoding = tiktoken.get_encoding(self.tiktoken_encoding) - def resolve(self, context: t.Dict) -> CallingMessages: + def resolve(self, context: t.Dict, tool_registry: t.Dict[str, ToolDefinition]) -> CallingMessages: pipe = {} prompt_budget = 0 ordered_pipe = dict((value, i) for i, value in enumerate(self.pipe)) diff --git a/lamoom/settings.py b/lamoom/settings.py index 1954188..b6c8de2 100644 --- a/lamoom/settings.py +++ b/lamoom/settings.py @@ -28,6 +28,7 @@ USE_API_SERVICE = parse_bool(os.environ.get("LAMOOM_USE_API_SERVICE", True)) LAMOOM_API_URI = os.environ.get("LAMOOM_API_URI") or os.environ.get("FLOW_PROMPT_API_URI") or "https://api.lamoom.com" +LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT = os.environ.get("LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT", 5) CACHE_PROMPT_FOR_EACH_SECONDS = int( os.environ.get("LAMOOM_CACHE_PROMPT_FOR_EACH_SECONDS", 5 * 60) ) # 5 minutes by default diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 4ba3308..839b590 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -44,13 +44,13 @@ def test_web_call(client): # with open('test_web_call_claude_3_7.txt', 'w', encoding="utf-8") as f: # f.write(json.dumps(result.messages, indent=4)) # assert result.content - # result = client.call(prompt.id, context, "openrouter/tngtech/deepseek-r1t-chimera:free", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - # assert result.content - # with open('test_web_call_openrouter_deepseek_r1.txt', 'w', encoding="utf-8") as f: - # f.write(json.dumps(result.messages, indent=4)) - - result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1") + result = client.call(prompt.id, context, "openrouter/tngtech/deepseek-r1t-chimera:free", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) assert result.content - with open('test_web_call_nebius_deepseek_r1.txt', 'w', encoding="utf-8") as f: + with open('test_web_call_openrouter_deepseek_r1.txt', 'w', encoding="utf-8") as f: f.write(json.dumps(result.messages, indent=4)) + + # result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1") + # assert result.content + # with open('test_web_call_nebius_deepseek_r1.txt', 'w', encoding="utf-8") as f: + # f.write(json.dumps(result.messages, indent=4)) assert 1 == 2 \ No newline at end of file From 71945d024a8599706a7228e435aeb005cdf8aba1 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 19:22:55 -0700 Subject: [PATCH 15/34] changed to custom providers --- .gitignore | 3 +- README.md | 7 +- lamoom/ai_models/ai_model.py | 36 +++--- lamoom/ai_models/attempt_to_call.py | 1 + lamoom/ai_models/openai/openai_models.py | 21 ++-- lamoom/ai_models/tools/base_tool.py | 39 +++--- lamoom/ai_models/tools/web_tool.py | 1 + lamoom/prompt/lamoom.py | 154 +++++++++++------------ lamoom/responses.py | 14 ++- lamoom/settings.py | 11 +- poetry.toml | 2 + pyproject.toml | 2 +- tests/prompts/test_model.py | 2 +- tests/prompts/test_pricing.py | 11 +- tests/prompts/test_prompt.py | 10 +- tests/prompts/test_web_call.py | 40 +++--- tests/test_integrational.py | 2 +- 17 files changed, 198 insertions(+), 158 deletions(-) create mode 100644 poetry.toml diff --git a/.gitignore b/.gitignore index bdcbb4f..8d82fd9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ dist .vscode .pytest_cache python -.env.test \ No newline at end of file +.env.test +tests/logs/* \ No newline at end of file diff --git a/README.md b/README.md index e903f54..a2f8fa3 100644 --- a/README.md +++ b/README.md @@ -138,11 +138,12 @@ response_llm = client.call(agent.id, context, model = "azure/useast/gpt-4o") ``` Custom model string format is the following: -`"custom/{model_name}"` -`provider_url` is required +`"custom/{provider_name}/{model_name}"` +where provider is provided in the env variable: +LAMOOM_CUSTOM_PROVIDERS={"provider_name": {"base_url": "https://","key":"key"}} ```python -response_llm = client.call(agent.id, context, model = "custom/o4-mini", provider_url = "your_model_url") +response_llm = client.call(agent.id, context, model = "custom/provider_name/model_name") ``` ### Lamoom Keys diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 9a8912a..0e64065 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -21,9 +21,10 @@ class AI_MODELS_PROVIDER(Enum): AZURE = "azure" CLAUDE = "claude" GEMINI = "gemini" - NEBIUS = "nebius" CUSTOM = "custom" - OPENROUTER = "openrouter" + + def is_custom(self): + return self == AI_MODELS_PROVIDER.CUSTOM encoding = tiktoken.get_encoding("cl100k_base") @@ -33,8 +34,12 @@ class AI_MODELS_PROVIDER(Enum): class AIModel: model: t.Optional[str] = '' tiktoken_encoding: t.Optional[str] = "cl100k_base" - provider: AI_MODELS_PROVIDER = None support_functions: bool = False + _provider_name: str = None + + @property + def provider_name(self): + return self.provider.value if not self.provider.is_custom() else self._provider_name @property def name(self) -> str: @@ -54,7 +59,7 @@ def call( current_messages: t.List[t.Dict[str, str]], max_tokens: t.Optional[int], tool_registry: t.Dict[str, ToolDefinition] = {}, - max_tool_iterations: int = 3, # Safety limit for sequential calls + max_tool_iterations: int = 5, # Safety limit for sequential calls stream_function: t.Callable = None, check_connection: t.Callable = None, stream_params: dict = {}, @@ -77,6 +82,7 @@ def call( attempts = max_tool_iterations while attempts > 0: try: + stream_response.update_to_another_attempt() stream_response = self.streaming( client=model_client, stream_response=stream_response, @@ -92,6 +98,8 @@ def call( logger.info(f'parsed_tool_call {parsed_tool_call}') if not parsed_tool_call or attempts <= 1: + stream_response.add_assistant_message() + self.save_call(stream_response, prompt, context, attempt=max_tool_iterations - attempts, client=client) attempts -= 1 continue # Execute tool call @@ -104,7 +112,7 @@ def call( continue stream_response.add_assistant_message() self.save_call(stream_response, prompt, context, test_data=test_data, client=client) - logger.info(f'Passing execution, finished. {attempts}') + logger.info(f'Passing execution {modelname}, finished. {attempts}') break except RetryableCustomError as e: logger.exception(f'RetryableCustomError {e}') @@ -115,22 +123,22 @@ def call( def handle_tool_call(self, tool_call: ToolCallResult, tool_registry: t.Dict[str, ToolDefinition]) -> str: """Handle a tool call by executing the corresponding function from the registry.""" - tool_name = tool_call.tool_name + function = tool_call.tool_name parameters = tool_call.parameters - tool_function = tool_registry.get(tool_name) + tool_function = tool_registry.get(function) if not tool_function: - logger.warning(f"Tool '{tool_name}' not found in registry") - return json.dumps({"error": f"Tool '{tool_name}' is not available."}) + logger.warning(f"Tool '{function}' not found in registry") + return json.dumps({"error": f"Tool '{function}' is not available."}) try: - logger.info(f"Executing tool '{tool_name}' with parameters: {parameters}") + logger.info(f"Executing tool '{function}' with parameters: {parameters}") result = tool_function.execution_function(**parameters) - logger.info(f"Tool '{tool_name}' executed successfully") + logger.info(f"Tool '{function}' executed successfully") tool_call.execution_result = result return json.dumps({"result": result}) except Exception as e: - result = f"Error executing tool '{tool_name}'" + result = f"Error executing tool '{function}', Please try second time." logger.exception(result, exc_info=e) tool_call.execution_result = result return json.dumps({"error": f"{result}: {str(e)}"}) @@ -158,7 +166,7 @@ def calculate_budget_for_text(self, text: str) -> int: return 0 return len(encoding.encode(text)) - def save_call(self, stream_response: StreamingResponse, prompt: "Prompt", context: str, attempt: int=0, test_data: dict = None, client: t.Any = None): + def save_call(self, stream_response: StreamingResponse, prompt: "Prompt", context: str, attempt: int=0, test_data: dict = {}, client: t.Any = None): sample_budget = self.calculate_budget_for_text( stream_response.get_message_str() @@ -174,7 +182,7 @@ def save_call(self, stream_response: StreamingResponse, prompt: "Prompt", contex if settings.USE_API_SERVICE and client.api_token: stream_response.id = f"{prompt.id}#{stream_response.started_tmst}" + (f"#{attempt}" if attempt else "") - self.worker.add_task( + client.worker.add_task( client.api_token, prompt.service_dump(), context, diff --git a/lamoom/ai_models/attempt_to_call.py b/lamoom/ai_models/attempt_to_call.py index 602bacb..650c5f4 100644 --- a/lamoom/ai_models/attempt_to_call.py +++ b/lamoom/ai_models/attempt_to_call.py @@ -14,6 +14,7 @@ class AttemptToCall: # ['function1', 'function2'] - if list of functions, only those functions will be called functions: t.List[str] = None attempt_number: int = 1 + api_key: str = None def __post_init__(self): self.id = ( diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 1ea862f..4c59801 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -28,9 +28,7 @@ class FamilyModel(Enum): BASE_URL_MAPPING = { - 'gemini': "https://generativelanguage.googleapis.com/v1beta/openai/", - 'nebius': 'https://api.studio.nebius.ai/v1/', - 'openrouter': 'https://openrouter.ai/api/v1', + 'gemini': "https://generativelanguage.googleapis.com/v1beta/openai/" } @@ -42,6 +40,7 @@ class OpenAIModel(AIModel): family: str = None max_sample_budget: int = C_4K base_url: str = None + api_key: str = None def __str__(self) -> str: return f"openai-{self.model}-{self.family}" @@ -73,9 +72,7 @@ def get_params(self) -> t.Dict[str, t.Any]: "model": self.model, } - def get_base_url(self) -> str | None: - return BASE_URL_MAPPING.get(self.provider.value, None) - + def is_provider_openai(self): return self.provider == AI_MODELS_PROVIDER.OPENAI @@ -84,15 +81,16 @@ def get_metrics_data(self): return { "model": self.model, "family": self.family, - "provider": self.provider.value, - "base_url": self.get_base_url() if self.base_url is None else self.base_url + "provider": self.provider.value if not self.provider.is_custom() else self.provider_name, + "base_url": self.base_url } def get_client(self, client_secrets: dict = {}): + base_url = client_secrets.get("base_url", None) return OpenAI( organization=client_secrets.get("organization", None), api_key=client_secrets["api_key"], - base_url=self.get_base_url() if self.base_url is None else self.base_url + base_url=client_secrets.get("base_url", None), ) def streaming( @@ -118,6 +116,7 @@ def streaming( } if max_tokens: call_kwargs["max_completion_tokens"] = min(max_tokens, self.max_sample_budget) + print(f"Calling OpenAI with params: {call_kwargs}") completion = client.chat.completions.create(**call_kwargs) for part in completion: if not part.choices: @@ -135,8 +134,8 @@ def streaming( if not delta or (not delta.content and getattr(delta, 'reasoning', None)): continue - content += delta.content - print(f'D {delta}') + if delta.content: + content += delta.content if getattr(delta, 'reasoning', None) and delta.reasoning: logger.debug(f'Adding reasoning {delta.reasoning}') stream_response.reasoning += delta.reasoning diff --git a/lamoom/ai_models/tools/base_tool.py b/lamoom/ai_models/tools/base_tool.py index 418b0f4..d311ffd 100644 --- a/lamoom/ai_models/tools/base_tool.py +++ b/lamoom/ai_models/tools/base_tool.py @@ -9,8 +9,8 @@ logger = logging.getLogger(__name__) -TOOL_CALL_NAME = 'tool_call' -TOOL_CALL_RESULT_NAME = 'tool_call_result' +TOOL_CALL_NAME = 'function_call' +TOOL_CALL_RESULT_NAME = 'function_result' # --- Constants for Prompting --- TOOL_CALL_START_TAG = f"<{TOOL_CALL_NAME}>" TOOL_CALL_END_TAG = f"" @@ -23,21 +23,24 @@ def get_tool_system_prompt(tool_descriptions: str, context: t.Dict[str, str]): ``` # Tool calling procedure -Before calling any tool, please follow procedure. You're doing unnecessary calls of tools. Make a mindset of what you need to do. +Before calling any function, please follow procedure. You're doing unnecessary calls of tools. Make a mindset of what you need to do. ## 1. Think out loud what you need to do ## 2. Provide 5 whys; -## 3. Call a tool; +## 3. Call a tool, you can call it when in ; -If you need to use a tool, use the next format: + +If you need to use a function, use the next exactly format. That format will be parsed from your answer: + ``` """ + TOOL_CALL_START_TAG + """ { -"tool_name": "...", +"function": "...", "parameters": { // parameters of the tool } } """ + TOOL_CALL_END_TAG + """ +``` """ @@ -108,9 +111,11 @@ def parse_tool_call_block(text_response: str) -> t.Optional[ToolCallResult]: Parses the block from the model's text response using regex. Returns a ToolCallResult object if a valid tool call is found, None otherwise. """ + logger.debug(f"Parsing tool call block: {text_response}") if not text_response: return None - + if not TOOL_CALL_END_TAG in text_response: + return None # Regex to find the block, allowing for whitespace variations # DOTALL allows '.' to match newlines within the JSON block json_content = text_response[text_response.find( @@ -123,23 +128,29 @@ def parse_tool_call_block(text_response: str) -> t.Optional[ToolCallResult]: try: parsed_data = json.loads(json_content) # Basic validation - if "tool_name" in parsed_data and ( + if "function" in parsed_data and ( "parameters" in parsed_data and isinstance(parsed_data["parameters"], dict) or "parameters" not in parsed_data ): - logger.info(f"Successfully parsed tool call: {parsed_data['tool_name']}") + logger.info(f"Successfully parsed tool call: {parsed_data['function']}") return ToolCallResult( content=text_response, has_tool_call=True, - tool_name=parsed_data.get("tool_name"), + tool_name=parsed_data.get("function"), parameters=parsed_data.get("parameters", {}), execution_result="" ) else: - logger.warning(f"Parsed JSON block lacks required 'tool_name' or 'parameters': {json_content}") + logger.warning(f"Parsed JSON block lacks required 'function' or 'parameters': {json_content}") return None except json.JSONDecodeError as e: logger.error(f"Failed to decode JSON from tool call block: {json_content}", exc_info=e) - return None + return ToolCallResult( + content=text_response, + has_tool_call=True, + tool_name=None, + parameters=None, + execution_result=str(e) + ) def call_function(tool_name: str, parameters: dict, tool_registry={}) -> str: @@ -179,8 +190,8 @@ def handle_tool_call(current_stream_part_content, tool_registry) -> ToolCallResu """ parsed_tool_call = parse_tool_call_block(current_stream_part_content) - if not parsed_tool_call: - return None + if not parsed_tool_call or parsed_tool_call.error: + return parsed_tool_call tool_name = parsed_tool_call.tool_name parameters = parsed_tool_call.parameters logger.info(f"Custom tool call block parsed: {tool_name}") diff --git a/lamoom/ai_models/tools/web_tool.py b/lamoom/ai_models/tools/web_tool.py index d5bbcbe..9ec2d21 100644 --- a/lamoom/ai_models/tools/web_tool.py +++ b/lamoom/ai_models/tools/web_tool.py @@ -8,6 +8,7 @@ import logging from lamoom.ai_models.tools.base_tool import ToolDefinition, ToolParameter +from lamoom.settings import LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT load_dotenv() diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 2d520ef..d2168bc 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -26,6 +26,9 @@ logger = logging.getLogger(__name__) +BASE_URL_MAPPING = { + 'gemini': "https://generativelanguage.googleapis.com/v1beta/openai/" +} @dataclass class Lamoom: @@ -35,9 +38,7 @@ class Lamoom: claude_key: str = None gemini_key: str = None azure_keys: t.Dict[str, str] = None - nebius_key: str = None - custom_key: str = None - openrouter_key: str = None + custom_keys: t.Dict[str, str] = None secrets: Secrets = None clients = {} @@ -50,6 +51,12 @@ def __post_init__(self): self.azure_keys = self.secrets.azure_keys else: logger.debug(f"Azure keys not found in secrets") + if not self.custom_keys: + if self.secrets.custom_keys: + logger.debug(f"Using Custom keys from secrets") + self.custom_keys = self.secrets.custom_keys + else: + logger.debug(f"Custom keys not found in secrets") if not self.api_token and self.secrets.API_TOKEN: logger.debug(f"Using API token from secrets") self.api_token = self.secrets.API_TOKEN @@ -65,41 +72,38 @@ def __post_init__(self): if not self.claude_key and self.secrets.CLAUDE_API_KEY: logger.debug(f"Using Claude API key from secrets") self.claude_key = self.secrets.CLAUDE_API_KEY - if not self.nebius_key and self.secrets.NEBIUS_API_KEY: - logger.debug(f"Using Nebius API key from secrets") - self.nebius_key = self.secrets.NEBIUS_API_KEY - if not self.openrouter_key and self.secrets.OPENROUTER_KEY: - logger.debug(f"Using OpenRouter API key from secrets") - self.openrouter_key = self.secrets.OPENROUTER_KEY - if not self.custom_key and self.secrets.CUSTOM_API_KEY: - logger.debug(f"Using Custom API key from secrets") - self.custom_key = self.secrets.CUSTOM_API_KEY self.service = LamoomService() if self.openai_key: - self.clients[AI_MODELS_PROVIDER.OPENAI] = { + self.clients[AI_MODELS_PROVIDER.OPENAI.value] = { "organization": self.openai_org, "api_key": self.openai_key, } if self.azure_keys: - if not self.clients.get(AI_MODELS_PROVIDER.AZURE): - self.clients[AI_MODELS_PROVIDER.AZURE] = {} + if not self.clients.get(AI_MODELS_PROVIDER.AZURE.value): + self.clients[AI_MODELS_PROVIDER.AZURE.value] = {} for realm, key_data in self.azure_keys.items(): - self.clients[AI_MODELS_PROVIDER.AZURE][realm] = { + self.clients[AI_MODELS_PROVIDER.AZURE.value][realm] = { "api_version": key_data.get("api_version", "2023-07-01-preview"), "azure_endpoint": key_data["url"], "api_key": key_data["key"], } logger.debug(f"Initialized Azure client for {realm} {key_data['url']}") if self.claude_key: - self.clients[AI_MODELS_PROVIDER.CLAUDE] = {"api_key": self.claude_key} + self.clients[AI_MODELS_PROVIDER.CLAUDE.value] = {"api_key": self.claude_key} if self.gemini_key: - self.clients[AI_MODELS_PROVIDER.GEMINI] = {"api_key": self.gemini_key} - if self.nebius_key: - self.clients[AI_MODELS_PROVIDER.NEBIUS] = {"api_key": self.nebius_key} - if self.openrouter_key: - self.clients[AI_MODELS_PROVIDER.OPENROUTER] = {"api_key": self.openrouter_key} - if self.custom_key: - self.clients[AI_MODELS_PROVIDER.CUSTOM] = {"api_key": self.custom_key} + self.clients[AI_MODELS_PROVIDER.GEMINI.value] = { + "api_key": self.gemini_key, + "base_url": BASE_URL_MAPPING.get(AI_MODELS_PROVIDER.GEMINI.value) + } + # Initialize custom providers from environment + for provider_name, provider_config in self.custom_keys.items(): + provider_key = f"custom_{provider_name}" + if provider_key not in self.clients: + self.clients[provider_key] = { + "api_key": provider_config.get("key"), + "base_url": provider_config.get("base_url") + } + logger.debug(f"Initialized custom provider {provider_key} {provider_config.get('base_url')}") self.worker = SaveWorker() def create_test( @@ -128,55 +132,43 @@ def create_test( else: logger.error(response) - def extract_provider_name(self, model: str, provider_url: str = None) -> dict: + def extract_provider_name(self, model: str) -> dict: parts = model.split("/") + + if "openai" in parts[0].lower() and len(parts) == 2: + return { + 'provider': parts[0].lower(), + 'model_name': parts[1] + } - if "azure" in parts[0].lower() and len(parts) == 3: + elif "azure" in parts[0].lower() and len(parts) == 3: model_provider, realm, model_name = parts return { 'provider': model_provider.lower(), 'model_name': model_name, 'realm': realm, - 'base_url': None - } - elif "nebius" in parts[0].lower() and len(parts) == 3: - model_provider = parts[0] - model_name = f"{parts[1]}/{parts[2]}" - return { - 'provider': model_provider.lower(), - 'model_name': model_name, - 'realm': None, - 'base_url': None } - elif "openrouter" in parts[0].lower() and len(parts) == 3: - model_provider = parts[0] - model_name = f"{parts[1]}/{parts[2]}" + elif "claude" in parts[0].lower() and len(parts) == 2: return { - 'provider': model_provider.lower(), - 'model_name': model_name, - 'realm': None, - 'base_url': None + 'provider': 'claude', + 'model_name': parts[1] } - elif "custom" in parts[0].lower(): - if len(parts) == 3: - model_provider = parts[0] - model_name = f"{parts[1]}/{parts[2]}" - else: - model_provider, model_name = parts + elif "gemini" in parts[0].lower() and len(parts) == 2: return { - 'provider': model_provider.lower(), - 'model_name': model_name, - 'realm': None, - 'base_url': provider_url - } - else: - model_provider, model_name = parts - return { - 'provider': model_provider.lower(), - 'model_name': model_name, - 'realm': None, - 'base_url': None + 'provider': 'gemini', + 'model_name': parts[1] } + elif "custom" in parts[0].lower() and len(parts) >= 3: + model_name = '/'.join(parts[2:]) + provider_name = parts[1].lower() + # Check if this is a registered custom provider + if provider_name in settings.LAMOOM_CUSTOM_PROVIDERS: + return { + 'provider': f"custom_{provider_name}", + 'model_name': model_name, + 'realm': None, + } + raise Exception(f"Unknown model: {model}") def get_default_context(self): return { @@ -201,12 +193,28 @@ def init_attempt(self, model_info: dict) -> AttemptToCall: ), weight=100, ) - elif provider == AI_MODELS_PROVIDER.CUSTOM.value: + elif provider == AI_MODELS_PROVIDER.OPENAI.value: + return AttemptToCall( + ai_model=OpenAIModel( + model=model_name + ), + weight=100, + ) + elif provider == AI_MODELS_PROVIDER.GEMINI.value: + return AttemptToCall( + ai_model=OpenAIModel( + model=model_name, + provider=AI_MODELS_PROVIDER.GEMINI, + ), + weight=100, + ) + elif provider.startswith('custom_'): + # Handle custom provider format return AttemptToCall( ai_model=OpenAIModel( model=model_name, provider=AI_MODELS_PROVIDER.CUSTOM, - base_url=model_info['base_url'] + _provider_name=model_info['provider'] ), weight=100, ) @@ -218,23 +226,15 @@ def init_attempt(self, model_info: dict) -> AttemptToCall: ), weight=100, ) - else: - return AttemptToCall( - ai_model=OpenAIModel( - provider=AI_MODELS_PROVIDER(provider), - model=model_name, - ), - weight=100, - ) - def init_behavior(self, model: str, provider_url: str = None) -> AIModelsBehaviour: - main_model_info = self.extract_provider_name(model, provider_url) + def init_behavior(self, model: str) -> AIModelsBehaviour: + main_model_info = self.extract_provider_name(model) main_attempt = self.init_attempt(main_model_info) fallback_attempts = [] for model in settings.FALLBACK_MODELS: - model_info = self.extract_provider_name(model, provider_url) + model_info = self.extract_provider_name(model) fallback_attempts.append(self.init_attempt(model_info)) return AIModelsBehaviour( @@ -247,7 +247,6 @@ def call( prompt_id: str, context: t.Dict[str, str], model: str, - provider_url: str = None, params: t.Dict[str, t.Any] = {}, version: str = None, count_of_retries: int = 5, @@ -263,7 +262,7 @@ def call( logger.debug(f"Calling {prompt_id}") prompt = self.get_prompt(prompt_id, version) - behaviour = self.init_behavior(model, provider_url) + behaviour = self.init_behavior(model) logger.info(behaviour) @@ -278,6 +277,7 @@ def call( messages = calling_messages.get_messages() messages = inject_tool_prompts(messages, list(prompt.tool_registry.values()), calling_context) + print(f'self.clients: {self.clients}, [current_attempt.ai_model.provider_name]: {current_attempt.ai_model.provider_name}') for _ in range(0, count_of_retries): try: result = current_attempt.ai_model.call( @@ -287,7 +287,7 @@ def call( stream_function=stream_function, check_connection=check_connection, stream_params=stream_params, - client_secrets=self.clients[current_attempt.ai_model.provider], + client_secrets=self.clients[current_attempt.ai_model.provider_name], modelname=model, prompt=prompt, context=json.dumps(context), diff --git a/lamoom/responses.py b/lamoom/responses.py index 5af81d6..edf689c 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -27,10 +27,10 @@ class Prompt: @dataclass class Metrics: - price_of_call: Decimal = None - sample_tokens_used: int = None - prompt_tokens_used: int = None - ai_model_details: dict = None + price_of_call: Decimal = 0 + sample_tokens_used: int = 0 + prompt_tokens_used: int = 0 + ai_model_details: dict = 0 latency: int = None @@ -64,6 +64,11 @@ class StreamingResponse(AIResponse): first_stream_tmst: int = None finished_tmst: int = None + def update_to_another_attempt(self): + self.is_detected_tool_call = False + self.content = '' + self.reasoning = '' + def set_streaming(self): if not self.started_tmst: self.started_tmst = current_timestamp_ms() @@ -82,7 +87,6 @@ def add_message(self, role: str, content: str): self.messages.append({"role": role, "content": content}) def add_tool_result(self, tool_result: ToolCallResult): - logger.info(f'TOOL_CALL: Added tool results {tool_result}') self.add_assistant_message() tool_result_message = format_tool_result_message(tool_result) self.content += tool_result_message diff --git a/lamoom/settings.py b/lamoom/settings.py index b6c8de2..479d3a4 100644 --- a/lamoom/settings.py +++ b/lamoom/settings.py @@ -28,7 +28,7 @@ USE_API_SERVICE = parse_bool(os.environ.get("LAMOOM_USE_API_SERVICE", True)) LAMOOM_API_URI = os.environ.get("LAMOOM_API_URI") or os.environ.get("FLOW_PROMPT_API_URI") or "https://api.lamoom.com" -LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT = os.environ.get("LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT", 5) +LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT = os.environ.get("LAMOOM_GOOGLE_SEARCH_RESULTS_COUNT", 3) CACHE_PROMPT_FOR_EACH_SECONDS = int( os.environ.get("LAMOOM_CACHE_PROMPT_FOR_EACH_SECONDS", 5 * 60) ) # 5 minutes by default @@ -40,6 +40,10 @@ FALLBACK_MODELS = [] +LAMOOM_CUSTOM_PROVIDERS: dict = json.loads(os.getenv("LAMOOM_CUSTOM_PROVIDERS", "{}")) +print(f'LAMOOM_CUSTOM_PROVIDERS: {LAMOOM_CUSTOM_PROVIDERS}') + + @dataclass class Secrets: API_TOKEN: str = field(default_factory=lambda: os.getenv("LAMOOM_API_TOKEN", os.getenv("FLOW_PROMPT_API_TOKEN"))) @@ -55,3 +59,8 @@ class Secrets: os.getenv("azure_keys", os.getenv("AZURE_OPENAI_KEYS", os.getenv("AZURE_KEYS", "{}"))) ) ) + custom_keys: dict = field( + default_factory=lambda: json.loads( + os.getenv("custom_keys", os.getenv("LAMOOM_CUSTOM_PROVIDERS", "{}")) + ) + ) diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..ab1033b --- /dev/null +++ b/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/pyproject.toml b/pyproject.toml index 173684e..2885260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.38" +version = "0.1.39a0" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" diff --git a/tests/prompts/test_model.py b/tests/prompts/test_model.py index 28a42a4..5fbc6e2 100644 --- a/tests/prompts/test_model.py +++ b/tests/prompts/test_model.py @@ -26,7 +26,7 @@ def test_model(client): prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') - result = client.call(prompt.id, context, "custom/deepseek-ai/DeepSeek-R1", provider_url="https://api.studio.nebius.ai/v1/") + result = client.call(prompt.id, context, "custom/nebius/deepseek-ai/DeepSeek-R1") assert result.content result = client.call(prompt.id, context, "openai/o4-mini") diff --git a/tests/prompts/test_pricing.py b/tests/prompts/test_pricing.py index 3d77c6f..1378ad1 100644 --- a/tests/prompts/test_pricing.py +++ b/tests/prompts/test_pricing.py @@ -36,11 +36,10 @@ def test_openai_pricing(lamoom_client: Lamoom): prompt = Prompt(id=prompt_id) prompt.add("{text}", role='user') - result_4o = lamoom_client.call(prompt.id, context, "openai/o4-mini", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) result_4o_mini = lamoom_client.call(prompt.id, context, "openai/o4-mini-mini", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - assert result_4o.metrics.price_of_call >= 0 - assert result_4o_mini.metrics.price_of_call >= 0 + assert result_4o_mini.metrics.prompt_tokens_used >= 0 + assert result_4o_mini.metrics.prompt_tokens_used >= 0 def test_claude_pricing(lamoom_client: Lamoom): @@ -57,6 +56,6 @@ def test_claude_pricing(lamoom_client: Lamoom): prompt.add("{text}", role='user') result_haiku = lamoom_client.call(prompt.id, context, "claude/claude-3-5-haiku-latest", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - result_sonnet = lamoom_client.call(prompt.id, context, "claude/claude-3-5-sonnet-latest", test_data={'ideal_answer': "There are eight", 'behavior_name': "gemini"}, stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) - - assert result_sonnet.metrics.price_of_call >= result_haiku.metrics.price_of_call \ No newline at end of file + + assert result_haiku.metrics.prompt_tokens_used >= 0 + assert result_haiku.metrics.sample_tokens_used >= 0 diff --git a/tests/prompts/test_prompt.py b/tests/prompts/test_prompt.py index 49d8187..02ee6ba 100644 --- a/tests/prompts/test_prompt.py +++ b/tests/prompts/test_prompt.py @@ -54,7 +54,7 @@ def test_prompt_initialize(azure_ai_attempt: AttemptToCall): user_prompt.add("Hello, how can I help you today?") context = {} - initialized_pipe = user_prompt.resolve(context) + initialized_pipe = user_prompt.resolve(context, {}) messages = initialized_pipe.messages assert len(messages) == 1 assert messages[0].content == "Hello, how can I help you today?" @@ -69,7 +69,7 @@ def test_prompt_initialize_not_enough_budget(azure_ai_attempt: AttemptToCall): user_prompt.min_sample_tokens = 1299 # Not enough tokens for the message user_prompt.model_max_tokens = 1300 # Not enough tokens for the message with pytest.raises(NotEnoughBudgetError): - user_prompt.resolve(context) + user_prompt.resolve(context, {}) def test_prompt_show_pipe(): @@ -89,7 +89,7 @@ def test_prompt_left_budget(azure_ai_attempt: AttemptToCall): user_prompt = pipe.create_prompt(azure_ai_attempt) user_prompt.model_max_tokens = 2030 user_prompt.reserved_tokens_budget_for_sampling = 2030 - initialized_pipe = user_prompt.resolve({}) + initialized_pipe = user_prompt.resolve({}, {}) assert ( initialized_pipe.left_budget == user_prompt.model_max_tokens @@ -104,7 +104,7 @@ def test_prompt_prompt_price(azure_ai_attempt: AttemptToCall): user_prompt = pipe.create_prompt(azure_ai_attempt) user_prompt.model_max_tokens = 4030 user_prompt.add("Hello " + 'world ' * 1000) - pipe = user_prompt.resolve({}) + pipe = user_prompt.resolve({}, {}) assert len(pipe.get_messages()) == 1 @@ -119,7 +119,7 @@ def test_prompt_calculate_budget_for_values(azure_ai_attempt: AttemptToCall): "no priority. didn't fit. Hello {name}" + ("hello" * 1000), priority=2 ) user_prompt = pipe.create_prompt(azure_ai_attempt) - prompt = user_prompt.resolve({"name": "World"}) + prompt = user_prompt.resolve({"name": "World"}, {}) messages = prompt.get_messages() assert len(messages) == 2 assert messages[0]["content"] == "Priority. Hello World" diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 839b590..57c7d30 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -30,27 +30,31 @@ def test_web_call(client): # initial version of the prompt prompt_id = 'test-web-search' prompt = Prompt(id=prompt_id) - prompt.add("{text}", role='system') + prompt.add("{text}", role='user') prompt.add_tool(WEB_SEARCH_TOOL) print(f'prompt.tool_registry: {prompt.tool_registry}') - client.api_token = None - # result = client.call(prompt.id, context, "openai/o4-mini") - # with open('test_web_call_openai_o4.txt', 'w', encoding="utf-8") as f: - # f.write(json.dumps(result.messages, indent=4 )) - # assert result.content - - # result = client.call(prompt.id, context, "claude/claude-3-7-sonnet-latest") - # with open('test_web_call_claude_3_7.txt', 'w', encoding="utf-8") as f: - # f.write(json.dumps(result.messages, indent=4)) - # assert result.content - result = client.call(prompt.id, context, "openrouter/tngtech/deepseek-r1t-chimera:free", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + result = client.call(prompt.id, context, "openai/o4-mini") + with open('tests/logs/test_web_call_openai_o4.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(result.messages, indent=4 )) assert result.content - with open('test_web_call_openrouter_deepseek_r1.txt', 'w', encoding="utf-8") as f: + + result = client.call(prompt.id, context, "claude/claude-3-7-sonnet-latest") + with open('tests/logs/test_web_call_claude_3_7.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(result.messages, indent=4)) + assert result.content + + result = client.call(prompt.id, context, "azure/useast/gpt-4o") + with open('tests/logs/test_web_call_azure_gpt_4o.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(result.messages, indent=4)) + assert result.content + + result = client.call(prompt.id, context, "custom/nvidia/deepseek-ai/deepseek-r1", stream_function=stream_function, check_connection=stream_check_connection, params={"stream": True}, stream_params={"validate": True, "end": "", "flush": True}) + assert result.content + with open('tests/logs/test_web_call_nvidia_deepseek_r1.txt', 'w', encoding="utf-8") as f: f.write(json.dumps(result.messages, indent=4)) - # result = client.call(prompt.id, context, "nebius/deepseek-ai/DeepSeek-R1") - # assert result.content - # with open('test_web_call_nebius_deepseek_r1.txt', 'w', encoding="utf-8") as f: - # f.write(json.dumps(result.messages, indent=4)) - assert 1 == 2 \ No newline at end of file + result = client.call(prompt.id, context, "custom/nebius/deepseek-ai/DeepSeek-R1") + assert result.content + with open('tests/logs/test_web_call_nebius_deepseek_r1.txt', 'w', encoding="utf-8") as f: + f.write(json.dumps(result.messages, indent=4)) diff --git a/tests/test_integrational.py b/tests/test_integrational.py index e93213c..a45fe36 100644 --- a/tests/test_integrational.py +++ b/tests/test_integrational.py @@ -54,4 +54,4 @@ def test_loading_prompt_from_service(client: Lamoom): result = client.call(prompt.id, context, "azure/useast/gpt-4o") # should call the prompt with music - assert result.messages[-1] == {'role': 'user', 'content': 'music1\nmusic2'} + assert result.messages[-2] == {'role': 'user', 'content': 'music1\nmusic2'} From 7736c8754118374ed5346fd03ffa973bb50a7e6a Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 19:32:18 -0700 Subject: [PATCH 16/34] add [tool.poetry.group.dev.dependencies] --- .github/workflows/run-unit-tests.yaml | 2 +- poetry.lock | 57 +++++++++++++++++++++++++-- pyproject.toml | 10 ++--- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index a5d0e3e..a0e8dff 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -46,7 +46,7 @@ jobs: python-version: 3.11 - name: Install Python libraries - run: poetry install + run: poetry install --with dev - name: Run tests with pytest run: poetry run make test \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 4854498..8ba5c2f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -377,11 +377,11 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "comm" @@ -685,6 +685,18 @@ perf = ["ipython"] test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + [[package]] name = "ipykernel" version = "6.29.5" @@ -1047,6 +1059,22 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.14.1)"] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "prompt-toolkit" version = "3.0.51" @@ -1276,6 +1304,29 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pytest" +version = "8.3.5" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, + {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1756,7 +1807,7 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, @@ -1934,4 +1985,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "d0b17a80d3318ef42785a6ded350d03c86ccccff4721d09b4bf3a68561296968" +content-hash = "06535ad3e480aa7ce553d1b7226aa691cebd04c6c562445fd825291875249846" diff --git a/pyproject.toml b/pyproject.toml index 2885260..5686a9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,8 +13,12 @@ openai = "^1.65.1" anthropic = "^0.31.2" httpx = "^0.27.2" beautifulsoup4 = "^4.13.4" +pytest = "^8.3.5" -[poetry.group.dev.dependencies] + +[tool.poetry.group.dev.dependencies] +black = "^24.4.2" +ipykernel = "^6.29.5" poetry = "^1.7.1" isort = "^5.13.2" flake8 = "^7.0.0" @@ -26,10 +30,6 @@ python-dotenv = "^1.0.1" twine = "^5.0.0" -[tool.poetry.group.dev.dependencies] -black = "^24.4.2" -ipykernel = "^6.29.5" - [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From c74351f7f1d741167da50222131c25702d46fbd6 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 19:36:18 -0700 Subject: [PATCH 17/34] poetry lock --- poetry.lock | 1241 +++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 +- 2 files changed, 1222 insertions(+), 22 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8ba5c2f..b6e31f3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -90,6 +90,22 @@ files = [ astroid = ["astroid (>=2,<4)"] test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] +[[package]] +name = "autopep8" +version = "2.3.2" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128"}, + {file = "autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758"}, +] + +[package.dependencies] +pycodestyle = ">=2.12.0" +tomli = {version = "*", markers = "python_version < \"3.11\""} + [[package]] name = "beautifulsoup4" version = "4.13.4" @@ -160,13 +176,61 @@ d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "build" +version = "1.2.2.post1" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, + {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} +packaging = ">=19.1" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0) ; python_version < \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.10\"", "setuptools (>=56.0.0) ; python_version == \"3.11\"", "setuptools (>=67.8.0) ; python_version >= \"3.12\"", "wheel (>=0.36.0)"] +typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] +uv = ["uv (>=0.1.18)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "cachecontrol" +version = "0.14.3" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "cachecontrol-0.14.3-py3-none-any.whl", hash = "sha256:b35e44a3113f17d2a31c1e6b27b9de6d4405f84ae51baa8c1d3cc5b633010cae"}, + {file = "cachecontrol-0.14.3.tar.gz", hash = "sha256:73e7efec4b06b20d9267b441c1f733664f989fb8688391b670ca812d70795d11"}, +] + +[package.dependencies] +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2,<2.0.0" +requests = ">=2.16.0" + +[package.extras] +dev = ["CacheControl[filecache,redis]", "build", "cherrypy", "codespell[tomli]", "furo", "mypy", "pytest", "pytest-cov", "ruff", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] +redis = ["redis (>=2.10.5)"] + [[package]] name = "certifi" version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -179,7 +243,7 @@ description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "implementation_name == \"pypy\"" +markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\" or sys_platform == \"darwin\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -253,13 +317,25 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "charset-normalizer" version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -355,6 +431,22 @@ files = [ {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] +[[package]] +name = "cleo" +version = "2.1.0" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +optional = false +python-versions = ">=3.7,<4.0" +groups = ["dev"] +files = [ + {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, + {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=3.0.0,<4.0.0" + [[package]] name = "click" version = "8.1.8" @@ -377,11 +469,11 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\""} [[package]] name = "comm" @@ -401,6 +493,148 @@ traitlets = ">=4" [package.extras] test = ["pytest"] +[[package]] +name = "coverage" +version = "7.8.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, + {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, + {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, + {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, + {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, + {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, + {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, + {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, + {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, + {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, + {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, + {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, + {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, + {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, + {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, + {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" +optional = false +python-versions = ">=3.7,<4.0" +groups = ["dev"] +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] + +[[package]] +name = "cryptography" +version = "43.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +markers = "sys_platform == \"linux\"" +files = [ + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "debugpy" version = "1.8.14" @@ -449,6 +683,18 @@ files = [ {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, ] +[[package]] +name = "distlib" +version = "0.3.9" +description = "Distribution utilities" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, +] + [[package]] name = "distro" version = "1.9.0" @@ -461,6 +707,106 @@ files = [ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "dulwich" +version = "0.21.7" +description = "Python Git Library" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, + {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, + {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, + {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, + {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, + {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, + {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, + {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, + {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, + {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, + {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, + {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, + {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, + {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, + {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, +] + +[package.dependencies] +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -492,13 +838,28 @@ files = [ [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] +[[package]] +name = "fastjsonschema" +version = "2.21.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, + {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + [[package]] name = "filelock" version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -509,6 +870,23 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3) testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +[[package]] +name = "flake8" +version = "7.2.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, + {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.13.0,<2.14.0" +pyflakes = ">=3.3.0,<3.4.0" + [[package]] name = "fsspec" version = "2025.3.2" @@ -645,13 +1023,28 @@ testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gr torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] +[[package]] +name = "identify" +version = "2.6.10" +description = "File identification library for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25"}, + {file = "identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -667,7 +1060,6 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version < \"3.10\"" files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, @@ -691,12 +1083,24 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] +[[package]] +name = "installer" +version = "0.7.0" +description = "A library for installing Python wheels." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, +] + [[package]] name = "ipykernel" version = "6.29.5" @@ -769,6 +1173,40 @@ qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +groups = ["dev"] +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + [[package]] name = "jedi" version = "0.19.2" @@ -789,6 +1227,23 @@ docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alab qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] +[[package]] +name = "jeepney" +version = "0.9.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +markers = "sys_platform == \"linux\"" +files = [ + {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, + {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, +] + +[package.extras] +test = ["async-timeout ; python_version < \"3.11\"", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["trio"] + [[package]] name = "jiter" version = "0.9.0" @@ -920,6 +1375,55 @@ traitlets = ">=5.3" docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] +[[package]] +name = "keyring" +version = "24.3.1" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, + {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab (>=1.1.0)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "matplotlib-inline" version = "0.1.7" @@ -935,6 +1439,116 @@ files = [ [package.dependencies] traitlets = "*" +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "more-itertools" +version = "10.7.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e"}, + {file = "more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3"}, +] + +[[package]] +name = "msgpack" +version = "1.1.0" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, +] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -959,6 +1573,52 @@ files = [ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] +[[package]] +name = "nh3" +version = "0.2.21" +description = "Python binding to Ammonia HTML sanitizer Rust crate" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286"}, + {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde"}, + {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243"}, + {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b"}, + {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251"}, + {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b"}, + {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9"}, + {file = "nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d"}, + {file = "nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82"}, + {file = "nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585"}, + {file = "nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293"}, + {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431"}, + {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa"}, + {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1"}, + {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283"}, + {file = "nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a"}, + {file = "nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629"}, + {file = "nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + [[package]] name = "openai" version = "1.75.0" @@ -1033,7 +1693,6 @@ description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" groups = ["dev"] -markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -1042,6 +1701,21 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" +[[package]] +name = "pkginfo" +version = "1.12.1.2" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pkginfo-1.12.1.2-py3-none-any.whl", hash = "sha256:c783ac885519cab2c34927ccfa6bf64b5a704d7c69afaea583dd9b7afe969343"}, + {file = "pkginfo-1.12.1.2.tar.gz", hash = "sha256:5cd957824ac36f140260964eba3c6be6442a8359b8c48f4adf90210f33a04b7b"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + [[package]] name = "platformdirs" version = "4.3.7" @@ -1065,7 +1739,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -1075,6 +1749,91 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "poetry" +version = "1.8.5" +description = "Python dependency management and packaging made easy." +optional = false +python-versions = "<4.0,>=3.8" +groups = ["dev"] +files = [ + {file = "poetry-1.8.5-py3-none-any.whl", hash = "sha256:5505fba69bf2a792b5d7402d21839c853644337392b745109b86a23010cce5f3"}, + {file = "poetry-1.8.5.tar.gz", hash = "sha256:eb2c88d224f58f36df8f7b36d6c380c07d1001bca28bde620f68fc086e881b70"}, +] + +[package.dependencies] +build = ">=1.0.3,<2.0.0" +cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]} +cleo = ">=2.1.0,<3.0.0" +crashtest = ">=0.4.1,<0.5.0" +dulwich = ">=0.21.2,<0.22.0" +fastjsonschema = ">=2.18.0,<3.0.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +installer = ">=0.7.0,<0.8.0" +keyring = ">=24.0.0,<25.0.0" +packaging = ">=23.1" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.12,<2.0" +platformdirs = ">=3.0.0,<5" +poetry-core = "1.9.1" +poetry-plugin-export = ">=1.6.0,<2.0.0" +pyproject-hooks = ">=1.0.0,<2.0.0" +requests = ">=2.26,<3.0" +requests-toolbelt = ">=1.0.0,<2.0.0" +shellingham = ">=1.5,<2.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.11.4,<1.0.0" +trove-classifiers = ">=2022.5.19" +virtualenv = ">=20.26.6,<21.0.0" +xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "1.9.1" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = "<4.0,>=3.8" +groups = ["dev"] +files = [ + {file = "poetry_core-1.9.1-py3-none-any.whl", hash = "sha256:6f45dd3598e0de8d9b0367360253d4c5d4d0110c8f5c71120a14f0e0f116c1a0"}, + {file = "poetry_core-1.9.1.tar.gz", hash = "sha256:7a2d49214bf58b4f17f99d6891d947a9836c9899a67a5069f52d7b67217f61b8"}, +] + +[[package]] +name = "poetry-plugin-export" +version = "1.8.0" +description = "Poetry plugin to export the dependencies to various formats" +optional = false +python-versions = "<4.0,>=3.8" +groups = ["dev"] +files = [ + {file = "poetry_plugin_export-1.8.0-py3-none-any.whl", hash = "sha256:adbe232cfa0cc04991ea3680c865cf748bff27593b9abcb1f35fb50ed7ba2c22"}, + {file = "poetry_plugin_export-1.8.0.tar.gz", hash = "sha256:1fa6168a85d59395d835ca564bc19862a7c76061e60c3e7dfaec70d50937fc61"}, +] + +[package.dependencies] +poetry = ">=1.8.0,<3.0.0" +poetry-core = ">=1.7.0,<3.0.0" + +[[package]] +name = "pre-commit" +version = "3.8.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "prompt-toolkit" version = "3.0.51" @@ -1121,7 +1880,6 @@ description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" groups = ["dev"] -markers = "sys_platform != \"win32\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -1142,6 +1900,18 @@ files = [ [package.extras] tests = ["pytest"] +[[package]] +name = "pycodestyle" +version = "2.13.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, + {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, +] + [[package]] name = "pycparser" version = "2.22" @@ -1149,7 +1919,7 @@ description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "implementation_name == \"pypy\"" +markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\" or sys_platform == \"darwin\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -1289,6 +2059,18 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pyflakes" +version = "3.3.2" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, + {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, +] + [[package]] name = "pygments" version = "2.19.1" @@ -1304,13 +2086,25 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, + {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, +] + [[package]] name = "pytest" version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["dev"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -1327,6 +2121,25 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1342,6 +2155,21 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.1.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, + {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pywin32" version = "310" @@ -1369,13 +2197,26 @@ files = [ {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1538,6 +2379,133 @@ files = [ [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} +[[package]] +name = "rapidfuzz" +version = "3.13.0" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107"}, + {file = "rapidfuzz-3.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-win32.whl", hash = "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f"}, + {file = "rapidfuzz-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-win32.whl", hash = "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e"}, + {file = "rapidfuzz-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-win32.whl", hash = "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264"}, + {file = "rapidfuzz-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc64da907114d7a18b5e589057e3acaf2fec723d31c49e13fedf043592a3f6a7"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4d9d7f84c8e992a8dbe5a3fdbea73d733da39bf464e62c912ac3ceba9c0cff93"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a79a2f07786a2070669b4b8e45bd96a01c788e7a3c218f531f3947878e0f956"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f338e71c45b69a482de8b11bf4a029993230760120c8c6e7c9b71760b6825a1"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb40ca8ddfcd4edd07b0713a860be32bdf632687f656963bcbce84cea04b8d8"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48719f7dcf62dfb181063b60ee2d0a39d327fa8ad81b05e3e510680c44e1c078"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9327a4577f65fc3fb712e79f78233815b8a1c94433d0c2c9f6bc5953018b3565"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:200030dfc0a1d5d6ac18e993c5097c870c97c41574e67f227300a1fb74457b1d"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cc269e74cad6043cb8a46d0ce580031ab642b5930562c2bb79aa7fbf9c858d26"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e62779c6371bd2b21dbd1fdce89eaec2d93fd98179d36f61130b489f62294a92"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f4797f821dc5d7c2b6fc818b89f8a3f37bcc900dd9e4369e6ebf1e525efce5db"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d21f188f6fe4fbf422e647ae9d5a68671d00218e187f91859c963d0738ccd88c"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-win32.whl", hash = "sha256:45dd4628dd9c21acc5c97627dad0bb791764feea81436fb6e0a06eef4c6dceaa"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:624a108122039af89ddda1a2b7ab2a11abe60c1521956f142f5d11bcd42ef138"}, + {file = "rapidfuzz-3.13.0-cp39-cp39-win_arm64.whl", hash = "sha256:435071fd07a085ecbf4d28702a66fd2e676a03369ee497cc38bcb69a46bc77e2"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99"}, + {file = "rapidfuzz-3.13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4"}, + {file = "rapidfuzz-3.13.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ccbd0e7ea1a216315f63ffdc7cd09c55f57851afc8fe59a74184cb7316c0598b"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50856f49a4016ef56edd10caabdaf3608993f9faf1e05c3c7f4beeac46bd12a"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd05336db4d0b8348d7eaaf6fa3c517b11a56abaa5e89470ce1714e73e4aca7"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573ad267eb9b3f6e9b04febce5de55d8538a87c56c64bf8fd2599a48dc9d8b77"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30fd1451f87ccb6c2f9d18f6caa483116bbb57b5a55d04d3ddbd7b86f5b14998"}, + {file = "rapidfuzz-3.13.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6dd36d4916cf57ddb05286ed40b09d034ca5d4bca85c17be0cb6a21290597d9"}, + {file = "rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8"}, +] + +[package.extras] +all = ["numpy"] + +[[package]] +name = "readme-renderer" +version = "44.0" +description = "readme_renderer is a library for rendering readme descriptions for Warehouse" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, + {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, +] + +[package.dependencies] +docutils = ">=0.21.2" +nh3 = ">=0.2.14" +Pygments = ">=2.5.1" + +[package.extras] +md = ["cmarkgfm (>=0.8.0)"] + [[package]] name = "regex" version = "2024.11.6" @@ -1648,7 +2616,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1664,6 +2632,85 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "rfc3986" +version = "2.0.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "rich" +version = "14.0.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["dev"] +files = [ + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +markers = "sys_platform == \"linux\"" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + [[package]] name = "six" version = "1.17.0" @@ -1807,8 +2854,8 @@ version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "python_version < \"3.11\"" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -1844,6 +2891,18 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + [[package]] name = "tornado" version = "6.4.2" @@ -1903,6 +2962,41 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "trove-classifiers" +version = "2025.5.9.12" +description = "Canonical source for classifiers on PyPI (pypi.org)." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "trove_classifiers-2025.5.9.12-py3-none-any.whl", hash = "sha256:e381c05537adac78881c8fa345fd0e9970159f4e4a04fcc42cfd3129cca640ce"}, + {file = "trove_classifiers-2025.5.9.12.tar.gz", hash = "sha256:7ca7c8a7a76e2cd314468c677c69d12cc2357711fcab4a60f87994c1589e5cb5"}, +] + +[[package]] +name = "twine" +version = "5.0.0" +description = "Collection of utilities for publishing packages on PyPI" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "twine-5.0.0-py3-none-any.whl", hash = "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0"}, + {file = "twine-5.0.0.tar.gz", hash = "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4"}, +] + +[package.dependencies] +importlib-metadata = ">=3.6" +keyring = ">=15.1" +pkginfo = ">=1.8.1" +readme-renderer = ">=35.0" +requests = ">=2.20" +requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" +rfc3986 = ">=1.4.0" +rich = ">=12.0.0" +urllib3 = ">=1.26.0" + [[package]] name = "typing-extensions" version = "4.13.2" @@ -1937,7 +3031,7 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, @@ -1949,6 +3043,27 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "virtualenv" +version = "20.31.2" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, + {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + [[package]] name = "wcwidth" version = "0.2.13" @@ -1961,6 +3076,93 @@ files = [ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] +[[package]] +name = "xattr" +version = "1.1.4" +description = "Python wrapper for extended filesystem attributes" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "sys_platform == \"darwin\"" +files = [ + {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:acb85b6249e9f3ea10cbb56df1021d43f4027212f0d004304bc9075dc7f54769"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1a848ab125c0fafdc501ccd83b4c9018bba576a037a4ca5960a22f39e295552e"}, + {file = "xattr-1.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:467ee77471d26ae5187ee7081b82175b5ca56ead4b71467ec2e6119d1b08beed"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fd35f46cb0154f7033f9d5d0960f226857acb0d1e0d71fd7af18ed84663007c"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d956478e9bb98a1efd20ebc6e5703497c1d2d690d5a13c4df4abf59881eed50"}, + {file = "xattr-1.1.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f25dfdcd974b700fb04a40e14a664a80227ee58e02ea062ac241f0d7dc54b4e"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33b63365c1fcbc80a79f601575bac0d6921732e0245b776876f3db3fcfefe22d"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:544542be95c9b49e211f0a463758f200de88ba6d5a94d3c4f42855a484341acd"}, + {file = "xattr-1.1.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac14c9893f3ea046784b7702be30889b200d31adcd2e6781a8a190b6423f9f2d"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bb4bbe37ba95542081890dd34fa5347bef4651e276647adaa802d5d0d7d86452"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3da489ecef798705f9a39ea8cea4ead0d1eeed55f92c345add89740bd930bab6"}, + {file = "xattr-1.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:798dd0cbe696635a6f74b06fc430818bf9c3b24314e1502eadf67027ab60c9b0"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2b6361626efad5eb5a6bf8172c6c67339e09397ee8140ec41258737bea9681"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7fa20a0c9ce022d19123b1c5b848d00a68b837251835a7929fe041ee81dcd0"}, + {file = "xattr-1.1.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20eeb08e2c57fc7e71f050b1cfae35cbb46105449853a582bf53fd23c5379e"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:477370e75821bded901487e5e752cffe554d1bd3bd4839b627d4d1ee8c95a093"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a8682091cd34a9f4a93c8aaea4101aae99f1506e24da00a3cc3dd2eca9566f21"}, + {file = "xattr-1.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2e079b3b1a274ba2121cf0da38bbe5c8d2fb1cc49ecbceb395ce20eb7d69556d"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ae6579dea05bf9f335a082f711d5924a98da563cac72a2d550f5b940c401c0e9"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd6038ec9df2e67af23c212693751481d5f7e858156924f14340376c48ed9ac7"}, + {file = "xattr-1.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:608b2877526674eb15df4150ef4b70b7b292ae00e65aecaae2f192af224be200"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54dad1a6a998c6a23edfd25e99f4d38e9b942d54e518570044edf8c767687ea"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0dab6ff72bb2b508f3850c368f8e53bd706585012676e1f71debba3310acde8"}, + {file = "xattr-1.1.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3c54c6af7cf09432b2c461af257d5f4b1cb2d59eee045f91bacef44421a46d"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e346e05a158d554639fbf7a0db169dc693c2d2260c7acb3239448f1ff4a9d67f"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3ff6d9e2103d0d6e5fcd65b85a2005b66ea81c0720a37036445faadc5bbfa424"}, + {file = "xattr-1.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7a2ee4563c6414dfec0d1ac610f59d39d5220531ae06373eeb1a06ee37cd193f"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878df1b38cfdadf3184ad8c7b0f516311128d5597b60ac0b3486948953658a83"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c9b8350244a1c5454f93a8d572628ff71d7e2fc2f7480dcf4c4f0e8af3150fe"}, + {file = "xattr-1.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a46bf48fb662b8bd745b78bef1074a1e08f41a531168de62b5d7bd331dadb11a"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83fc3c07b583777b1dda6355329f75ca6b7179fe0d1002f1afe0ef96f7e3b5de"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6308b19cff71441513258699f0538394fad5d66e1d324635207a97cb076fd439"}, + {file = "xattr-1.1.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c00ddc15ddadc9c729cd9504dabf50adb3d9c28f647d4ac9a3df45a046b1a0"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a06136196f26293758e1b244200b73156a0274af9a7349fa201c71c7af3bb9e8"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8fc2631a3c6cfcdc71f7f0f847461839963754e76a2015de71e7e71e3304abc0"}, + {file = "xattr-1.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6e1e835f9c938d129dd45e7eb52ebf7d2d6816323dab93ce311bf331f7d2328"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:60dea2d369a6484e8b7136224fc2971e10e2c46340d83ab780924afe78c90066"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85c2b778b09d919523f80f244d799a142302582d76da18903dc693207c4020b0"}, + {file = "xattr-1.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ee0abba9e1b890d39141714ff43e9666864ca635ea8a5a2194d989e6b17fe862"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4174ba7f51f46b95ea7918d907c91cd579575d59e6a2f22ca36a0551026737"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2b05e52e99d82d87528c54c2c5c8c5fb0ba435f85ac6545511aeea136e49925"}, + {file = "xattr-1.1.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a3696fad746be37de34eb73c60ea67144162bd08106a5308a90ce9dea9a3287"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3a7149439a26b68904c14fdc4587cde4ac7d80303e9ff0fefcfd893b698c976"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:507b36a126ce900dbfa35d4e2c2db92570c933294cba5d161ecd6a89f7b52f43"}, + {file = "xattr-1.1.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9392b417b54923e031041940d396b1d709df1d3779c6744454e1f1c1f4dad4f5"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e9f00315e6c02943893b77f544776b49c756ac76960bea7cb8d7e1b96aefc284"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8f98775065260140efb348b1ff8d50fd66ddcbf0c685b76eb1e87b380aaffb3"}, + {file = "xattr-1.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b471c6a515f434a167ca16c5c15ff34ee42d11956baa749173a8a4e385ff23e7"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee0763a1b7ceb78ba2f78bee5f30d1551dc26daafcce4ac125115fa1def20519"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:099e6e9ce7999b403d36d9cf943105a3d25d8233486b54ec9d1b78623b050433"}, + {file = "xattr-1.1.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e56faef9dde8d969f0d646fb6171883693f88ae39163ecd919ec707fbafa85"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:328156d4e594c9ae63e1072503c168849e601a153ad37f0290743544332d6b6f"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a57a55a27c7864d6916344c9a91776afda6c3b8b2209f8a69b79cdba93fbe128"}, + {file = "xattr-1.1.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3c19cdde08b040df1e99d2500bf8a9cff775ab0e6fa162bf8afe6d84aa93ed04"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c72667f19d3a9acf324aed97f58861d398d87e42314731e7c6ab3ac7850c971"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:67ae934d75ea2563fc48a27c5945749575c74a6de19fdd38390917ddcb0e4f24"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1b0c348dd8523554dc535540d2046c0c8a535bb086561d8359f3667967b6ca"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22284255d2a8e8f3da195bd8e8d43ce674dbc7c38d38cb6ecfb37fae7755d31f"}, + {file = "xattr-1.1.4-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aac5ef4381c26d3ce147ca98fba5a78b1e5bcd6be6755b4908659f2705c6d"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:803f864af528f6f763a5be1e7b1ccab418e55ae0e4abc8bda961d162f850c991"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:40354ebfb5cecd60a5fbb9833a8a452d147486b0ffec547823658556625d98b5"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2abaf5d06be3361bfa8e0db2ee123ba8e92beab5bceed5e9d7847f2145a32e04"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e638e5ffedc3565242b5fa3296899d35161bad771f88d66277b58f03a1ba9fe"}, + {file = "xattr-1.1.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0597e919d116ec39997804288d77bec3777228368efc0f2294b84a527fc4f9c2"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee9455c501d19f065527afda974418b3ef7c61e85d9519d122cd6eb3cb7a00"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:89ed62ce430f5789e15cfc1ccabc172fd8b349c3a17c52d9e6c64ecedf08c265"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b824f4b9259cd8bb6e83c4873cf8bf080f6e4fa034a02fe778e07aba8d345"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fba66faa0016dfc0af3dd7ac5782b5786a1dfb851f9f3455e266f94c2a05a04"}, + {file = "xattr-1.1.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ec4b0c3e0a7bcd103f3cf31dd40c349940b2d4223ce43d384a3548992138ef1"}, + {file = "xattr-1.1.4.tar.gz", hash = "sha256:b7b02ecb2270da5b7e7deaeea8f8b528c17368401c2b9d5f63e91f545b45d372"}, +] + +[package.dependencies] +cffi = ">=1.16.0" + +[package.extras] +test = ["pytest"] + [[package]] name = "zipp" version = "3.21.0" @@ -1968,7 +3170,6 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version < \"3.10\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, @@ -1985,4 +3186,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "06535ad3e480aa7ce553d1b7226aa691cebd04c6c562445fd825291875249846" +content-hash = "ab93dc205dbc53207844558db3ba7d65afdc7d3e9682a82e4b25d31b1c1f8a6d" diff --git a/pyproject.toml b/pyproject.toml index 5686a9c..2b5d4c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ openai = "^1.65.1" anthropic = "^0.31.2" httpx = "^0.27.2" beautifulsoup4 = "^4.13.4" -pytest = "^8.3.5" [tool.poetry.group.dev.dependencies] @@ -22,7 +21,7 @@ ipykernel = "^6.29.5" poetry = "^1.7.1" isort = "^5.13.2" flake8 = "^7.0.0" -pytest = "^7.1.2" +pytest = "^8.3.5" pytest-cov = "^3.0.0" pre-commit = "^3.6.0" autopep8 = "^2.0.4" From 16a5dc393d9f91de0bb2a1f9eb13f71f1f3b09ec Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 19:39:28 -0700 Subject: [PATCH 18/34] add def custom_keys as {} --- lamoom/prompt/lamoom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index d2168bc..d3940c6 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -1,7 +1,7 @@ from datetime import datetime import logging import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, field import requests from lamoom.ai_models.tools.base_tool import inject_tool_prompts from lamoom.settings import LAMOOM_API_URI @@ -38,7 +38,7 @@ class Lamoom: claude_key: str = None gemini_key: str = None azure_keys: t.Dict[str, str] = None - custom_keys: t.Dict[str, str] = None + custom_keys: t.Dict[str, str] = field(default_factory=dict) secrets: Secrets = None clients = {} From 85706a2e7cda2b7875b808208a8d0a7b16ceb10c Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 19:54:01 -0700 Subject: [PATCH 19/34] add def custom_keys as {} --- .github/workflows/run-unit-tests.yaml | 1 + lamoom/settings.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/run-unit-tests.yaml b/.github/workflows/run-unit-tests.yaml index a0e8dff..bc812bd 100644 --- a/.github/workflows/run-unit-tests.yaml +++ b/.github/workflows/run-unit-tests.yaml @@ -19,6 +19,7 @@ jobs: echo OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }} >> .env echo LAMOOM_API_URI=${{ secrets.LAMOOM_API_URI }} >> .env echo LAMOOM_API_TOKEN=${{ secrets.LAMOOM_API_TOKEN }} >> .env + echo LAMOOM_CUSTOM_PROVIDERS=${{ secrets.LAMOOM_CUSTOM_PROVIDERS }} >> .env echo NEBIUS_API_KEY=${{ secrets.NEBIUS_API_KEY }} >> .env echo CUSTOM_API_KEY=${{ secrets.CUSTOM_API_KEY }} >> .env echo GOOGLE_API_KEY=${{ secrets.GOOGLE_API_KEY }} >> .env diff --git a/lamoom/settings.py b/lamoom/settings.py index 479d3a4..2219291 100644 --- a/lamoom/settings.py +++ b/lamoom/settings.py @@ -40,10 +40,6 @@ FALLBACK_MODELS = [] -LAMOOM_CUSTOM_PROVIDERS: dict = json.loads(os.getenv("LAMOOM_CUSTOM_PROVIDERS", "{}")) -print(f'LAMOOM_CUSTOM_PROVIDERS: {LAMOOM_CUSTOM_PROVIDERS}') - - @dataclass class Secrets: API_TOKEN: str = field(default_factory=lambda: os.getenv("LAMOOM_API_TOKEN", os.getenv("FLOW_PROMPT_API_TOKEN"))) From d730d1ea97378c96524a848578f173336568598f Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 21:44:53 -0700 Subject: [PATCH 20/34] updated files path --- .gitignore | 3 +-- lamoom/prompt/lamoom.py | 2 +- pyproject.toml | 2 +- tests/logs/.gitgnore | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 tests/logs/.gitgnore diff --git a/.gitignore b/.gitignore index 8d82fd9..bdcbb4f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,4 @@ dist .vscode .pytest_cache python -.env.test -tests/logs/* \ No newline at end of file +.env.test \ No newline at end of file diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index d3940c6..6a468dd 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -162,7 +162,7 @@ def extract_provider_name(self, model: str) -> dict: model_name = '/'.join(parts[2:]) provider_name = parts[1].lower() # Check if this is a registered custom provider - if provider_name in settings.LAMOOM_CUSTOM_PROVIDERS: + if provider_name in settings.Secrets.custom_keys: return { 'provider': f"custom_{provider_name}", 'model_name': model_name, diff --git a/pyproject.toml b/pyproject.toml index 2b5d4c3..94eade0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.39a0" +version = "0.1.39" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" diff --git a/tests/logs/.gitgnore b/tests/logs/.gitgnore new file mode 100644 index 0000000..2211df6 --- /dev/null +++ b/tests/logs/.gitgnore @@ -0,0 +1 @@ +*.txt From 3215888d7adfd73c69f689a5f7cbd6ef2d7861ba Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 14 May 2025 21:53:10 -0700 Subject: [PATCH 21/34] updated files path --- lamoom/prompt/lamoom.py | 2 +- lamoom/settings.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 6a468dd..d3940c6 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -162,7 +162,7 @@ def extract_provider_name(self, model: str) -> dict: model_name = '/'.join(parts[2:]) provider_name = parts[1].lower() # Check if this is a registered custom provider - if provider_name in settings.Secrets.custom_keys: + if provider_name in settings.LAMOOM_CUSTOM_PROVIDERS: return { 'provider': f"custom_{provider_name}", 'model_name': model_name, diff --git a/lamoom/settings.py b/lamoom/settings.py index 2219291..fd38334 100644 --- a/lamoom/settings.py +++ b/lamoom/settings.py @@ -38,6 +38,9 @@ SHOULD_INCLUDE_REASONING = parse_bool(os.environ.get("SHOULD_INCLUDE_REASONING", True)) PIPE_PROMPTS = {} FALLBACK_MODELS = [] +LAMOOM_CUSTOM_PROVIDERS = json.loads( + os.getenv("custom_keys", os.getenv("LAMOOM_CUSTOM_PROVIDERS", "{}")) +) @dataclass From ea264d460d04e71d961cbce9da75ee4a946366be Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 15 May 2025 22:27:24 -0700 Subject: [PATCH 22/34] added StopStreamingError --- lamoom/ai_models/ai_model.py | 10 ++++++++-- lamoom/exceptions.py | 4 ++++ pyproject.toml | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 0e64065..b6a29cc 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -11,7 +11,7 @@ from lamoom import settings from lamoom.ai_models.tools.base_tool import ToolCallResult, ToolDefinition, parse_tool_call_block from lamoom.responses import AIResponse, StreamingResponse -from lamoom.exceptions import RetryableCustomError +from lamoom.exceptions import RetryableCustomError, StopStreamingError from lamoom.utils import current_timestamp_ms logger = logging.getLogger(__name__) @@ -117,7 +117,13 @@ def call( except RetryableCustomError as e: logger.exception(f'RetryableCustomError {e}') attempts -= 1 - continue + continue + except StopStreamingError as e: + logger.exception(f'StopStreamingError {e}') + stream_response.add_assistant_message() + self.save_call(stream_response, prompt, context, attempt=max_tool_iterations - attempts, client=client) + logger.info(f'Passing execution {modelname}, finished. {attempts}') + break return stream_response diff --git a/lamoom/exceptions.py b/lamoom/exceptions.py index 8797504..75f8d42 100644 --- a/lamoom/exceptions.py +++ b/lamoom/exceptions.py @@ -6,6 +6,10 @@ class RetryableCustomError(LamoomError): pass +class StopStreamingError(LamoomError): + pass + + class LamoomPromptIsnotFoundError(LamoomError): pass diff --git a/pyproject.toml b/pyproject.toml index 94eade0..975b347 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.39" +version = "0.1.40a0" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" From a842068c7d9cb1be2c2bfe8d8f67d942e6a62bc7 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 15 May 2025 22:27:57 -0700 Subject: [PATCH 23/34] added new version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 975b347..f74db7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a0" +version = "0.1.40a1" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" From 104ff4f22c986b7c25a6efc9a389ea7b8281d838 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 15 May 2025 22:41:30 -0700 Subject: [PATCH 24/34] handle StopStreamingError --- lamoom/ai_models/ai_model.py | 4 ++++ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index b6a29cc..92cf527 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -143,6 +143,10 @@ def handle_tool_call(self, tool_call: ToolCallResult, tool_registry: t.Dict[str, logger.info(f"Tool '{function}' executed successfully") tool_call.execution_result = result return json.dumps({"result": result}) + except StopStreamingError as e: + logger.exception(f"Tool '{function}' execution stopped: {e}") + tool_call.execution_result = str(e) + raise e except Exception as e: result = f"Error executing tool '{function}', Please try second time." logger.exception(result, exc_info=e) diff --git a/pyproject.toml b/pyproject.toml index f74db7d..9bc2498 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a1" +version = "0.1.40a2" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" From 2f66633faef07175640da095bbe2fbbe966553e9 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 15 May 2025 23:26:23 -0700 Subject: [PATCH 25/34] added another raise --- lamoom/ai_models/ai_model.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 92cf527..5d133d7 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -122,8 +122,8 @@ def call( logger.exception(f'StopStreamingError {e}') stream_response.add_assistant_message() self.save_call(stream_response, prompt, context, attempt=max_tool_iterations - attempts, client=client) - logger.info(f'Passing execution {modelname}, finished. {attempts}') - break + logger.info(f'Failing execution {modelname} w/ StopStreamingError, finished. {attempts}') + raise e return stream_response diff --git a/pyproject.toml b/pyproject.toml index 9bc2498..e71f290 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a2" +version = "0.1.40a3" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" From 18d46c2c52dcabefa690d9a2eefbdabdcb4c140d Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Wed, 21 May 2025 13:51:09 -0700 Subject: [PATCH 26/34] 0.1.40a16 --- lamoom/ai_models/ai_model.py | 188 ++++++++++++++++++++++- lamoom/ai_models/claude/claude_model.py | 9 +- lamoom/ai_models/openai/openai_models.py | 15 +- lamoom/prompt/lamoom.py | 13 +- pyproject.toml | 2 +- tests/test_ai_model_tag_parser.py | 159 +++++++++++++++++++ 6 files changed, 375 insertions(+), 11 deletions(-) create mode 100644 tests/test_ai_model_tag_parser.py diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 5d133d7..955eca5 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,7 +1,6 @@ - import json import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum import logging from _decimal import Decimal @@ -29,6 +28,143 @@ def is_custom(self): encoding = tiktoken.get_encoding("cl100k_base") +class TagParser: + """Parser for handling streaming content with ignore tags.""" + MAX_BUFFER_SIZE = 50 + + def __init__(self, ignore_tags: t.List[str] = None): + self.ignore_tags = set(ignore_tags or []) + self.reset() + + def reset(self): + self.state = { + 'buffer': '', + 'in_ignored_tag': False, + 'ignored_tag': None, + 'partial_tag_buffer': '', + } + + def _is_partial_ignored_tag(self, buffer: str) -> bool: + if not buffer.startswith('<'): + return False + for tag in self.ignore_tags: + if tag.startswith(buffer[1:]): + return True + return False + + def _is_valid_tag(self, tag: str) -> bool: + if not (tag.startswith('<') and tag.endswith('>')): + return False + tag_content = tag[1:-1].strip() + if tag_content.startswith('/'): + return len(tag_content) > 1 + return len(tag_content) > 0 + + def text_to_stream_chunk(self, chunk: str) -> str: + return chunk + logger = logging.getLogger("TagParser") + logger.debug(f"[INPUT] chunk: {chunk!r}") + + # Always process buffer first if it exists + if self.state['buffer']: + logger.debug(f"[BUFFER] Prepending buffer: {self.state['buffer']!r} to chunk: {chunk!r}") + chunk = self.state['buffer'] + chunk + self.state['buffer'] = '' + + # If we're in an ignored tag, just buffer everything + if self.state['in_ignored_tag']: + self.state['buffer'] += chunk + close_tag = f'' + idx = self.state['buffer'].find(close_tag) + if idx == -1: + if len(self.state['buffer']) > self.MAX_BUFFER_SIZE: + self.state['buffer'] = self.state['buffer'][-self.MAX_BUFFER_SIZE:] + logger.debug(f"[IGNORED] Still in ignored tag: {self.state['ignored_tag']!r}, buffer: {self.state['buffer']!r}") + return '' + self.state['buffer'] = self.state['buffer'][idx + len(close_tag):] + logger.debug(f"[IGNORED] Closed ignored tag: {self.state['ignored_tag']!r}") + self.state['in_ignored_tag'] = False + self.state['ignored_tag'] = None + # After closing ignored tag, process the rest of the buffer in the next call + return '' + + # Handle partial tag buffer + if self.state['partial_tag_buffer']: + self.state['partial_tag_buffer'] += chunk + if '\n' in self.state['partial_tag_buffer']: + result = self.state['partial_tag_buffer'] + self.state['partial_tag_buffer'] = '' + logger.debug(f"[PARTIAL] Newline in partial tag buffer, output: {result!r}") + return result + if '>' in self.state['partial_tag_buffer']: + gt_idx = self.state['partial_tag_buffer'].find('>') + tag = self.state['partial_tag_buffer'][:gt_idx + 1] + rest = self.state['partial_tag_buffer'][gt_idx + 1:] + if self._is_valid_tag(tag): + tag_name = tag[1:-1].strip().split()[0] + if tag_name in self.ignore_tags: + self.state['in_ignored_tag'] = True + self.state['ignored_tag'] = tag_name + self.state['partial_tag_buffer'] = '' + logger.debug(f"[PARTIAL] Entered ignored tag: {tag_name!r}") + return '' + else: + result = self.state['partial_tag_buffer'] + self.state['partial_tag_buffer'] = '' + logger.debug(f"[PARTIAL] Outputting non-ignored tag: {result!r}") + return result + else: + result = self.state['partial_tag_buffer'] + self.state['partial_tag_buffer'] = '' + logger.debug(f"[PARTIAL] Outputting invalid tag: {result!r}") + return result + if self._is_partial_ignored_tag(self.state['partial_tag_buffer']): + logger.debug(f"[PARTIAL] Buffering possible ignored tag: {self.state['partial_tag_buffer']!r}") + return '' + else: + result = self.state['partial_tag_buffer'] + self.state['partial_tag_buffer'] = '' + logger.debug(f"[PARTIAL] Outputting not-ignored tag: {result!r}") + return result + + # Main logic: flush up to the first ignored tag, then stop processing further in this call + output = '' + i = 0 + while i < len(chunk): + c = chunk[i] + if c == '<': + # Check if this could be the start of an ignored tag + for tag in self.ignore_tags: + if chunk[i+1:i+1+len(tag)] == tag: + # Found start of ignored tag + self.state['in_ignored_tag'] = True + self.state['ignored_tag'] = tag + self.state['buffer'] = chunk[i:] # Buffer the rest for next call + logger.debug(f"[MAIN] Entered ignored tag: {tag!r}, output: {output!r}, buffer: {self.state['buffer']!r}") + return output + # Could be a partial ignored tag + for tag in self.ignore_tags: + if tag.startswith(chunk[i+1:]): + self.state['partial_tag_buffer'] = chunk[i:] + logger.debug(f"[MAIN] Buffering possible partial ignored tag: {self.state['partial_tag_buffer']!r}") + return output + # Not an ignored tag, output up to and including this char + if output: + logger.debug(f"[MAIN] Output before non-ignored tag: {output!r}") + return output + else: + output += '<' + i += 1 + continue + elif c == '\n': + output += '\n' + i += 1 + continue + else: + output += c + i += 1 + logger.debug(f"[MAIN] Final output: {output!r}") + return output @dataclass(kw_only=True) class AIModel: @@ -36,6 +172,53 @@ class AIModel: tiktoken_encoding: t.Optional[str] = "cl100k_base" support_functions: bool = False _provider_name: str = None + stream_ignore_tags: t.List[str] = field(default_factory=list) + _tag_parser: TagParser = field(init=False, default=None) + + def __post_init__(self): + self._tag_parser = TagParser(self.stream_ignore_tags) + + def _should_stream_content(self, content: str) -> bool: + """Determine if content should be streamed based on ignore tags""" + return bool(self._tag_parser.text_to_stream_chunk(content)) + + def text_to_stream_chunk(self, chunk: str) -> str: + # If we have a buffered tag, prepend it to chunk + if self.state['tag_buffer']: + # If we find a newline, reset buffer and continue + if '\n' in chunk: + newline_pos = chunk.find('\n') + self.state['tag_buffer'] = '' # Reset buffer on newline + return self.text_to_stream_chunk(chunk[newline_pos + 1:]) + + chunk = self.state['tag_buffer'] + chunk + self.state['tag_buffer'] = '' + + if not chunk: + return '' + + # Split only on newlines to handle them separately + for separator in ['\n', '\r\n']: + if not separator in chunk: + continue + text_to_stream = [] + lines = chunk.split(separator) + for i, line in enumerate(lines): + if i < len(lines) - 1: + line += separator + processed = self._process_chunk(line) + print(f'processed: {processed}, buffer: {self.state["tag_buffer"]}') + if processed: + text_to_stream.append(processed) + return ''.join(text_to_stream) + + processed = self._process_chunk(chunk) + print(f'processed: {processed}, buffer: {self.state["tag_buffer"]}') + return processed + + def _reset_tag_parser(self): + """Reset tag parser state""" + self._tag_parser.reset() @property def provider_name(self): @@ -72,6 +255,7 @@ def call( **kwargs, ) -> AIResponse: """Common call implementation that handles streaming and tool calls.""" + # self._reset_tag_parser() # Reset parser state for new call model_client = self.get_client(client_secrets) # Prepare streaming response stream_response = StreamingResponse( diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index f4ebefc..c02e2e9 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -71,7 +71,6 @@ def streaming( content = "" try: - unified_messages = self.unify_messages_with_same_role(stream_response.messages) call_kwargs = { "model": self.model, @@ -95,9 +94,12 @@ def streaming( stream_response.set_streaming() content += text_chunk + + # Only stream content if not in ignored tag if stream_function: - stream_function(text_chunk, **stream_params) - + if self._should_stream_content(text_chunk) and not tool_call_started: + stream_function(text_chunk, **stream_params) + # Check for tool call markers if tool_call_started and TOOL_CALL_END_TAG in content: stream_response.is_detected_tool_call = True @@ -108,6 +110,7 @@ def streaming( if not tool_call_started: tool_call_started = True continue + stream_response.content = content stream_response.set_finish_reason(FINISH_REASON_FINISH) return stream_response diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 4c59801..e3cde51 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -134,21 +134,32 @@ def streaming( if not delta or (not delta.content and getattr(delta, 'reasoning', None)): continue + if delta.content: + if stream_function: + text_to_stream = self.text_to_stream_chunk(delta.content) + if text_to_stream: + stream_function(text_to_stream, **stream_params) content += delta.content + + if getattr(delta, 'reasoning', None) and delta.reasoning: logger.debug(f'Adding reasoning {delta.reasoning}') stream_response.reasoning += delta.reasoning - if stream_function: - stream_function(delta.content, **stream_params) + if tool_call_started and TOOL_CALL_END_TAG in content: logger.info(f'tool_call_ended: {content}') stream_response.is_detected_tool_call = True stream_response.content = content break + if check_connection and not check_connection(**stream_params): raise ConnectionLostError("Connection was lost!") + if stream_function: + text_to_stream = self.text_to_stream_chunk('') + if text_to_stream: + stream_function(text_to_stream, **stream_params) stream_response.content = content return stream_response diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index d3940c6..933e1c0 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -15,7 +15,8 @@ from lamoom.exceptions import ( LamoomPromptIsnotFoundError, - RetryableCustomError + RetryableCustomError, + StopStreamingError ) from lamoom.services.SaveWorker import SaveWorker from lamoom.prompt.prompt import Prompt @@ -97,6 +98,7 @@ def __post_init__(self): } # Initialize custom providers from environment for provider_name, provider_config in self.custom_keys.items(): + logger.info(f"Initializing custom provider {provider_name} {provider_config.get('base_url')}") provider_key = f"custom_{provider_name}" if provider_key not in self.clients: self.clients[provider_key] = { @@ -162,7 +164,7 @@ def extract_provider_name(self, model: str) -> dict: model_name = '/'.join(parts[2:]) provider_name = parts[1].lower() # Check if this is a registered custom provider - if provider_name in settings.LAMOOM_CUSTOM_PROVIDERS: + if provider_name in self.custom_keys: return { 'provider': f"custom_{provider_name}", 'model_name': model_name, @@ -301,11 +303,16 @@ def call( f"Attempt failed: {prompt_attempts.current_attempt} with retryable error: {e}" ) break + except StopStreamingError as e: + logger.exception( + f"Attempt Stopped: {prompt_attempts.current_attempt} with non-retryable error: {e}" + ) + raise e except Exception as e: logger.exception( f"Attempt failed: {prompt_attempts.current_attempt} with non-retryable error: {e}" ) - break + raise e logger.exception( "Prompt call failed, no attempts worked" diff --git a/pyproject.toml b/pyproject.toml index e71f290..4926bd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a3" +version = "0.1.40a16" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" diff --git a/tests/test_ai_model_tag_parser.py b/tests/test_ai_model_tag_parser.py new file mode 100644 index 0000000..118c732 --- /dev/null +++ b/tests/test_ai_model_tag_parser.py @@ -0,0 +1,159 @@ +import pytest +from unittest.mock import Mock, patch +from lamoom.ai_models.ai_model import AIModel, TagParser +from lamoom.responses import StreamingResponse +from lamoom.ai_models.tools.base_tool import TOOL_CALL_START_TAG, TOOL_CALL_END_TAG + +class TestTagParser: + @pytest.fixture + def parser(self): + return TagParser(ignore_tags=['think', 'reason']) + + def test_basic_text(self, parser): + # Test text without tags + assert parser.text_to_stream_chunk("Hello world") == "Hello world" + assert parser.text_to_stream_chunk("") == "" + + def test_ignored_tags(self, parser): + # Test ignored tags + assert parser.text_to_stream_chunk("Hello world") == "Hello " + assert parser.text_to_stream_chunk("") == "" + print(parser.state) + assert parser.text_to_stream_chunk("Hello worldworld there") == "Hello " + print(parser.state) + assert parser.text_to_stream_chunk("") == " there" + print(parser.state) + + def test_non_ignored_tags(self, parser): + # Test non-ignored tags + assert parser.text_to_stream_chunk("Hello world") == "Hello world" + assert parser.text_to_stream_chunk("Hello world") == "Hello " + print(parser.state) + assert parser.text_to_stream_chunk("") == '' + + def test_partial_tags(self, parser): + # Test partial tag handling + assert parser.text_to_stream_chunk("") == "" # Complete ignored tag + assert parser.text_to_stream_chunk("content") == "" # Content in ignored tag + assert parser.text_to_stream_chunk("") == "" # Close ignored tag + assert parser.text_to_stream_chunk("after") == "after" # Content after ignored tag + + # Test partial non-ignored tag + assert parser.text_to_stream_chunk("hey ") == "hey " # Output partial non-ignored tag + assert parser.text_to_stream_chunk("<") == "<" # Output partial non-ignored tag + assert parser.text_to_stream_chunk("o") == "o" # Output partial non-ignored tag + assert parser.text_to_stream_chunk("ther>") == "ther>" # Complete non-ignored tag + + +class TestAIModelTagStreaming: + @pytest.fixture + def model(self): + return AIModel(stream_ignore_tags=['think', 'code']) + + @pytest.fixture + def mock_stream_function(self): + return Mock() + + @pytest.fixture + def stream_response(self): + return StreamingResponse(messages=[], tool_registry={}) + + def test_basic_streaming(self, model, mock_stream_function, stream_response): + # Test basic streaming with ignored tags + chunks = [ + "Hello ", + "", + "ignored", + "", + " world" + ] + + for chunk in chunks: + if model._should_stream_content(chunk): + mock_stream_function(chunk) + + # Only non-ignored content should be streamed + assert mock_stream_function.call_count == 2 + mock_stream_function.assert_any_call("Hello ") + mock_stream_function.assert_any_call(" world") + + def test_tool_calls_with_tags(self, model, mock_stream_function, stream_response): + # Test tool calls with ignored tags + content = f"Before {TOOL_CALL_START_TAG}ignored{TOOL_CALL_END_TAG} After" + + # Simulate streaming + for chunk in content: + if model._should_stream_content(chunk): + mock_stream_function(chunk) + + # Tool call content should be accumulated but not streamed if in ignored tag + assert mock_stream_function.call_count > 0 + assert "Before" in "".join(call[0][0] for call in mock_stream_function.call_args_list) + assert "After" in "".join(call[0][0] for call in mock_stream_function.call_args_list) + + def test_nested_streaming(self, model, mock_stream_function, stream_response): + # Test nested tag streaming + chunks = [ + "Start ", + "", + "outer ", + "", + "inner", + "", + " more", + "", + " End" + ] + + for chunk in chunks: + if model._should_stream_content(chunk): + mock_stream_function(chunk) + + # Only content outside ignored tags should be streamed + assert mock_stream_function.call_count == 2 + mock_stream_function.assert_any_call("Start ") + mock_stream_function.assert_any_call(" End") + + def test_partial_tag_streaming(self, model, mock_stream_function, stream_response): + # Test streaming with partial tags + chunks = [ + "Start ", + "", + "ignored", + "", + " End" + ] + + for chunk in chunks: + if model._should_stream_content(chunk): + mock_stream_function(chunk) + + print(mock_stream_function.call_args_list) + # Partial tags should be handled correctly + assert mock_stream_function.call_count == 2 + mock_stream_function.assert_any_call("Start ") + mock_stream_function.assert_any_call(" End") + + def test_newline_streaming(self, model, mock_stream_function, stream_response): + # Test streaming with newlines + chunks = [ + "Line 1\n", + "ignored\n", + "Line 2" + ] + + for chunk in chunks: + if model._should_stream_content(chunk): + mock_stream_function(chunk) + print(mock_stream_function.call_args_list) + # Newlines should break tags and be streamed + assert mock_stream_function.call_count == 2 + assert "Line 1\n" in "".join(call[0][0] for call in mock_stream_function.call_args_list) + assert "\nLine 2" in "".join(call[0][0] for call in mock_stream_function.call_args_list) From 939d45babb605c102f2f0575a314d7426feb3399 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 22 May 2025 15:02:35 -0700 Subject: [PATCH 27/34] Updated tests for tags writing --- lamoom/ai_models/ai_model.py | 312 ++++++++++++------------ lamoom/ai_models/claude/claude_model.py | 3 +- tests/test_ai_model_tag_parser.py | 79 +++--- 3 files changed, 208 insertions(+), 186 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 955eca5..20ea85a 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -1,4 +1,5 @@ import json +import re import typing as t from dataclasses import dataclass, field from enum import Enum @@ -32,139 +33,174 @@ class TagParser: """Parser for handling streaming content with ignore tags.""" MAX_BUFFER_SIZE = 50 - def __init__(self, ignore_tags: t.List[str] = None): - self.ignore_tags = set(ignore_tags or []) + def __init__(self, ignore_tags: t.List[str] = None, writing_tags: t.List[str] = None): + self.ignore_tags = list(set(ignore_tags or [])) + self.writing_tags = list(set(writing_tags or [])) self.reset() def reset(self): self.state = { 'buffer': '', - 'in_ignored_tag': False, 'ignored_tag': None, - 'partial_tag_buffer': '', + 'writing_tag': None, + 'tags': [], } - - def _is_partial_ignored_tag(self, buffer: str) -> bool: - if not buffer.startswith('<'): - return False - for tag in self.ignore_tags: - if tag.startswith(buffer[1:]): - return True - return False - - def _is_valid_tag(self, tag: str) -> bool: - if not (tag.startswith('<') and tag.endswith('>')): - return False - tag_content = tag[1:-1].strip() - if tag_content.startswith('/'): - return len(tag_content) > 1 - return len(tag_content) > 0 + if self.writing_tags: + self.state['in_writing_tag'] = False + else: + self.state['in_writing_tag'] = True + if self.ignore_tags: + self.state['in_ignored_tag'] = False + else: + self.state['in_ignored_tag'] = True + + def parse_tags(self, chunk: str) -> t.List[str]: + matches = [] + ignored_length = len(self.ignore_tags) + for i, tag in enumerate(self.ignore_tags + self.writing_tags): + is_ignored = i < ignored_length + opening_tag = f'<{tag}(>| )' + closing_tag = f'' + for match in re.finditer(opening_tag, chunk): + matched_tag = { + 'tag': tag, 'type': 'opening', + 'position': match.start(), + 'is_ignored': is_ignored, + 'end_position': match.end() + } + matches.append(matched_tag) + logger.debug(f"[PARSE_TAGS] match {opening_tag}: {matched_tag}") + for match in re.finditer(closing_tag, chunk): + matched_tag = { + 'tag': tag, 'type': 'closing', + 'position': match.start(), + 'is_ignored': is_ignored, + 'end_position': match.end() + } + matches.append(matched_tag) + logger.debug(f"[PARSE_TAGS] closing match: {matched_tag}") + matches.sort(key=lambda x: x['position']) + matches.append({'tag': None, 'type': 'end', 'position': len(chunk), + 'is_ignored': False, 'end_position': len(chunk)}) + return matches def text_to_stream_chunk(self, chunk: str) -> str: - return chunk + # Split only on newlines to handle them separately + for separator in ['<']: + if not separator in chunk: + continue + text_to_stream = [] + lines = chunk.split(separator) + for i, line in enumerate(lines): + if not line and i == 0: + continue + if i != 0: + line = separator + line + processed = self._text_to_stream_chunk(line) + if processed: + text_to_stream.append(processed) + return ''.join(text_to_stream) + + processed = self._text_to_stream_chunk(chunk) + return processed + + + def _text_to_stream_chunk(self, incoming_chunk: str) -> str: logger = logging.getLogger("TagParser") - logger.debug(f"[INPUT] chunk: {chunk!r}") + logger.debug(f"[INPUT] chunk: {incoming_chunk!r}: {self.state['buffer']}, tags: {self.state['tags']}") # Always process buffer first if it exists - if self.state['buffer']: - logger.debug(f"[BUFFER] Prepending buffer: {self.state['buffer']!r} to chunk: {chunk!r}") - chunk = self.state['buffer'] + chunk - self.state['buffer'] = '' - - # If we're in an ignored tag, just buffer everything - if self.state['in_ignored_tag']: - self.state['buffer'] += chunk - close_tag = f'' - idx = self.state['buffer'].find(close_tag) - if idx == -1: - if len(self.state['buffer']) > self.MAX_BUFFER_SIZE: - self.state['buffer'] = self.state['buffer'][-self.MAX_BUFFER_SIZE:] - logger.debug(f"[IGNORED] Still in ignored tag: {self.state['ignored_tag']!r}, buffer: {self.state['buffer']!r}") - return '' - self.state['buffer'] = self.state['buffer'][idx + len(close_tag):] - logger.debug(f"[IGNORED] Closed ignored tag: {self.state['ignored_tag']!r}") - self.state['in_ignored_tag'] = False - self.state['ignored_tag'] = None - # After closing ignored tag, process the rest of the buffer in the next call + chunk = self.state['buffer'] + incoming_chunk + self.state['buffer'] = '' + if '<' in chunk and not '>' in chunk and (chunk.rfind('<') - len(chunk)) < 5 and incoming_chunk: + # wait to get more data + self.state['buffer'] = chunk + logger.debug(f"[STREAM] waiting for more data: {chunk}") + return '' + if '<' not in chunk: + logger.debug(f"[STREAM] not '<' in chunk: {chunk}, in_ignored_tag: {self.state['in_ignored_tag']}, in_writing_tag: {self.state['in_writing_tag']}") + if not self.state['in_ignored_tag'] and self.state['in_writing_tag']: + return chunk + elif '>' not in chunk and incoming_chunk and len(chunk) < self.MAX_BUFFER_SIZE: + self.state['buffer'] = chunk return '' - # Handle partial tag buffer - if self.state['partial_tag_buffer']: - self.state['partial_tag_buffer'] += chunk - if '\n' in self.state['partial_tag_buffer']: - result = self.state['partial_tag_buffer'] - self.state['partial_tag_buffer'] = '' - logger.debug(f"[PARTIAL] Newline in partial tag buffer, output: {result!r}") - return result - if '>' in self.state['partial_tag_buffer']: - gt_idx = self.state['partial_tag_buffer'].find('>') - tag = self.state['partial_tag_buffer'][:gt_idx + 1] - rest = self.state['partial_tag_buffer'][gt_idx + 1:] - if self._is_valid_tag(tag): - tag_name = tag[1:-1].strip().split()[0] - if tag_name in self.ignore_tags: - self.state['in_ignored_tag'] = True - self.state['ignored_tag'] = tag_name - self.state['partial_tag_buffer'] = '' - logger.debug(f"[PARTIAL] Entered ignored tag: {tag_name!r}") - return '' - else: - result = self.state['partial_tag_buffer'] - self.state['partial_tag_buffer'] = '' - logger.debug(f"[PARTIAL] Outputting non-ignored tag: {result!r}") - return result + if len(chunk) > self.MAX_BUFFER_SIZE: + # always output what wasn't added in FIFO buffer if it's not in ignored_tag; + chunk_to_process = chunk[:len(chunk) - self.MAX_BUFFER_SIZE] + if not self.state['in_ignored_tag'] and self.state['in_writing_tag']: + chunk = chunk[len(chunk) - self.MAX_BUFFER_SIZE:] + return chunk_to_process + + tag_matches = self.parse_tags(chunk) + last_match_index = 0 + # not_ignored, ignored + for match in tag_matches: + value = chunk[last_match_index:match['position']] + value_till_end = chunk[last_match_index:match['end_position']] + last_match_index = match['end_position'] + is_ignored = self.state['in_ignored_tag'] + in_writing_tag = self.state['in_writing_tag'] + + logger.debug(f"[STREAM] match {match['type']} {match['tag']}: {value}, ignored: {is_ignored}, writing: {in_writing_tag}") + if match['type'] == 'opening': + self.state['tags'].append(match['tag']) + if match['is_ignored']: + self.state['ignored_tag'] = match['tag'] + self.state['in_ignored_tag'] = True else: - result = self.state['partial_tag_buffer'] - self.state['partial_tag_buffer'] = '' - logger.debug(f"[PARTIAL] Outputting invalid tag: {result!r}") - return result - if self._is_partial_ignored_tag(self.state['partial_tag_buffer']): - logger.debug(f"[PARTIAL] Buffering possible ignored tag: {self.state['partial_tag_buffer']!r}") - return '' - else: - result = self.state['partial_tag_buffer'] - self.state['partial_tag_buffer'] = '' - logger.debug(f"[PARTIAL] Outputting not-ignored tag: {result!r}") - return result - - # Main logic: flush up to the first ignored tag, then stop processing further in this call - output = '' - i = 0 - while i < len(chunk): - c = chunk[i] - if c == '<': - # Check if this could be the start of an ignored tag - for tag in self.ignore_tags: - if chunk[i+1:i+1+len(tag)] == tag: - # Found start of ignored tag - self.state['in_ignored_tag'] = True - self.state['ignored_tag'] = tag - self.state['buffer'] = chunk[i:] # Buffer the rest for next call - logger.debug(f"[MAIN] Entered ignored tag: {tag!r}, output: {output!r}, buffer: {self.state['buffer']!r}") - return output - # Could be a partial ignored tag - for tag in self.ignore_tags: - if tag.startswith(chunk[i+1:]): - self.state['partial_tag_buffer'] = chunk[i:] - logger.debug(f"[MAIN] Buffering possible partial ignored tag: {self.state['partial_tag_buffer']!r}") - return output - # Not an ignored tag, output up to and including this char - if output: - logger.debug(f"[MAIN] Output before non-ignored tag: {output!r}") - return output + # TODO add writing tag closing like '>' which can be in inf characters + self.state['writing_tag'] = match['tag'] + self.state['in_writing_tag'] = True + + if not value: + continue + if not is_ignored and in_writing_tag: + logger.debug(f"[STREAM] Writing match: {match}: {value}: {self.state}") + self.state['buffer'] = chunk[match['end_position']:] + return value + logger.debug(f"[STREAM][NOT WRITING] {match}: {value}: {self.state}") + + if match['type'] == 'closing': + try: + index_of_closing_tag = len(self.state['tags']) - self.state['tags'][::-1].index(match['tag']) - 1 + except ValueError: + index_of_closing_tag = len(self.state['tags']) + self.state['buffer'] = chunk[match['end_position']:] + logger.debug(f"[STREAM] Closing match: {match}: {value} index_of_opening_tag: {index_of_closing_tag}, buffer {self.state['buffer']}") + # if it was not opened + if index_of_closing_tag == len(self.state['tags']): + logger.debug(f"[STREAM] Closing tag not opened: {match}: {value}") + if in_writing_tag and not is_ignored: + return value_till_end + return '' + pop_all_tags_starting_from_closed_tag = self.state['tags'][index_of_closing_tag:] + self.state['tags'] = self.state['tags'][:index_of_closing_tag] + logger.debug(f"[STREAM] self.state['tags']: {self.state['tags']}") + # what should we do with chunk? + partial_chunk = value + if match['is_ignored']: + list_tags = [tag for tag in self.state['tags'] if tag in self.ignore_tags] + self.state['in_ignored_tag'] = bool(list_tags) + self.state['ignored_tag'] = list_tags[-1] if list_tags else None else: - output += '<' - i += 1 + list_tags = [tag for tag in self.state['tags'] if tag in self.writing_tags] + self.state['in_writing_tag'] = bool(list_tags) + self.state['writing_tag'] = list_tags[-1] if list_tags else None + logger.debug(f"[STREAM] in_ignored { self.state['in_ignored_tag']}, in_writing_tag: {self.state['in_writing_tag']}") + if not partial_chunk: + logger.debug(f"[STREAM] closing tag. partial_chunk: {partial_chunk}: {self.state}. Continueing") continue - elif c == '\n': - output += '\n' - i += 1 - continue - else: - output += c - i += 1 - logger.debug(f"[MAIN] Final output: {output!r}") - return output + if not is_ignored and in_writing_tag: + return partial_chunk + + if match['type'] == 'end': + self.state['buffer'] = '' + if in_writing_tag and not is_ignored: + logger.debug(f"[STREAM] End match: {match}: {value}") + return value + self.state['buffer'] = chunk[last_match_index:] + return '' @dataclass(kw_only=True) class AIModel: @@ -178,48 +214,16 @@ class AIModel: def __post_init__(self): self._tag_parser = TagParser(self.stream_ignore_tags) - def _should_stream_content(self, content: str) -> bool: - """Determine if content should be streamed based on ignore tags""" - return bool(self._tag_parser.text_to_stream_chunk(content)) - - def text_to_stream_chunk(self, chunk: str) -> str: - # If we have a buffered tag, prepend it to chunk - if self.state['tag_buffer']: - # If we find a newline, reset buffer and continue - if '\n' in chunk: - newline_pos = chunk.find('\n') - self.state['tag_buffer'] = '' # Reset buffer on newline - return self.text_to_stream_chunk(chunk[newline_pos + 1:]) - - chunk = self.state['tag_buffer'] + chunk - self.state['tag_buffer'] = '' - - if not chunk: - return '' - - # Split only on newlines to handle them separately - for separator in ['\n', '\r\n']: - if not separator in chunk: - continue - text_to_stream = [] - lines = chunk.split(separator) - for i, line in enumerate(lines): - if i < len(lines) - 1: - line += separator - processed = self._process_chunk(line) - print(f'processed: {processed}, buffer: {self.state["tag_buffer"]}') - if processed: - text_to_stream.append(processed) - return ''.join(text_to_stream) - - processed = self._process_chunk(chunk) - print(f'processed: {processed}, buffer: {self.state["tag_buffer"]}') - return processed - def _reset_tag_parser(self): """Reset tag parser state""" self._tag_parser.reset() + def text_to_stream_chunk(self, chunk: str) -> str: + """Process incoming chunk of text and return the parsed result.""" + if not self._tag_parser: + raise ValueError("Tag parser is not initialized.") + return self._tag_parser.text_to_stream_chunk(chunk) + @property def provider_name(self): return self.provider.value if not self.provider.is_custom() else self._provider_name diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index c02e2e9..0e2a4c7 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -97,7 +97,8 @@ def streaming( # Only stream content if not in ignored tag if stream_function: - if self._should_stream_content(text_chunk) and not tool_call_started: + text_chunk = self.chunk_to_stream(text_chunk) + if text_chunk and not tool_call_started: stream_function(text_chunk, **stream_params) # Check for tool call markers diff --git a/tests/test_ai_model_tag_parser.py b/tests/test_ai_model_tag_parser.py index 118c732..78eb36a 100644 --- a/tests/test_ai_model_tag_parser.py +++ b/tests/test_ai_model_tag_parser.py @@ -15,18 +15,19 @@ def test_basic_text(self, parser): assert parser.text_to_stream_chunk("") == "" def test_ignored_tags(self, parser): + parser.reset() # Test ignored tags - assert parser.text_to_stream_chunk("Hello world") == "Hello " + assert parser.text_to_stream_chunk("Hello world" ) == "Hello " assert parser.text_to_stream_chunk("") == "" - print(parser.state) assert parser.text_to_stream_chunk("Hello worldworld there") == "Hello " - print(parser.state) - assert parser.text_to_stream_chunk("") == " there" - print(parser.state) + # still in ignored tag reason + assert parser.text_to_stream_chunk("Hello world there") == "" + assert parser.text_to_stream_chunk("") == "" # Close ignored tag + assert parser.text_to_stream_chunk(" there") == " there" def test_non_ignored_tags(self, parser): + parser.reset() # Test non-ignored tags assert parser.text_to_stream_chunk("Hello world") == "Hello world" assert parser.text_to_stream_chunk("Hello world") == "Hello " @@ -34,6 +35,7 @@ def test_non_ignored_tags(self, parser): assert parser.text_to_stream_chunk("") == '' def test_partial_tags(self, parser): + parser.reset() # Test partial tag handling assert parser.text_to_stream_chunk("") == "" # Complete ignored tag @@ -43,9 +45,9 @@ def test_partial_tags(self, parser): # Test partial non-ignored tag assert parser.text_to_stream_chunk("hey ") == "hey " # Output partial non-ignored tag - assert parser.text_to_stream_chunk("<") == "<" # Output partial non-ignored tag - assert parser.text_to_stream_chunk("o") == "o" # Output partial non-ignored tag - assert parser.text_to_stream_chunk("ther>") == "ther>" # Complete non-ignored tag + assert parser.text_to_stream_chunk("<") == "" # Output partial non-ignored tag + assert parser.text_to_stream_chunk("o") == "" # Output partial non-ignored tag + assert parser.text_to_stream_chunk("ther>") == "" # Complete non-ignored tag class TestAIModelTagStreaming: @@ -68,12 +70,15 @@ def test_basic_streaming(self, model, mock_stream_function, stream_response): "", "ignored", "", - " world" + " world", + "" ] for chunk in chunks: - if model._should_stream_content(chunk): - mock_stream_function(chunk) + stream_chunk = model.text_to_stream_chunk(chunk) + if not stream_chunk: + continue + mock_stream_function(stream_chunk) # Only non-ignored content should be streamed assert mock_stream_function.call_count == 2 @@ -86,8 +91,10 @@ def test_tool_calls_with_tags(self, model, mock_stream_function, stream_response # Simulate streaming for chunk in content: - if model._should_stream_content(chunk): - mock_stream_function(chunk) + stream_chunk = model.text_to_stream_chunk(chunk) + if not stream_chunk: + continue + mock_stream_function(stream_chunk) # Tool call content should be accumulated but not streamed if in ignored tag assert mock_stream_function.call_count > 0 @@ -105,12 +112,15 @@ def test_nested_streaming(self, model, mock_stream_function, stream_response): "", " more", "", - " End" + " End", + "" ] for chunk in chunks: - if model._should_stream_content(chunk): - mock_stream_function(chunk) + stream_chunk = model.text_to_stream_chunk(chunk) + if not stream_chunk: + continue + mock_stream_function(stream_chunk) # Only content outside ignored tags should be streamed assert mock_stream_function.call_count == 2 @@ -126,12 +136,15 @@ def test_partial_tag_streaming(self, model, mock_stream_function, stream_respons "ignored", "", - " End" + " End", + "" ] for chunk in chunks: - if model._should_stream_content(chunk): - mock_stream_function(chunk) + stream_chunk = model.text_to_stream_chunk(chunk) + if not stream_chunk: + continue + mock_stream_function(stream_chunk) print(mock_stream_function.call_args_list) # Partial tags should be handled correctly @@ -142,18 +155,22 @@ def test_partial_tag_streaming(self, model, mock_stream_function, stream_respons def test_newline_streaming(self, model, mock_stream_function, stream_response): # Test streaming with newlines chunks = [ - "Line 1\n", - "ignored\n", - "Line 2" + "Line 1<<", + "ignored", + "Line 2<<", + "" ] for chunk in chunks: - if model._should_stream_content(chunk): - mock_stream_function(chunk) + stream_chunk = model.text_to_stream_chunk(chunk) + if not stream_chunk: + continue + mock_stream_function(stream_chunk) + print(mock_stream_function.call_args_list) # Newlines should break tags and be streamed - assert mock_stream_function.call_count == 2 - assert "Line 1\n" in "".join(call[0][0] for call in mock_stream_function.call_args_list) - assert "\nLine 2" in "".join(call[0][0] for call in mock_stream_function.call_args_list) + assert mock_stream_function.call_count == 4 + assert "Line 1<<" in "".join(call[0][0] for call in mock_stream_function.call_args_list) + assert "Line 2<<" in "".join(call[0][0] for call in mock_stream_function.call_args_list) From 57b2f934eadbb6513cc9adaba299ff9d08496c5e Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 22 May 2025 15:04:19 -0700 Subject: [PATCH 28/34] Updated tests for tags writing --- tests/test_ai_model_tag_parser.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_ai_model_tag_parser.py b/tests/test_ai_model_tag_parser.py index 78eb36a..5a58d11 100644 --- a/tests/test_ai_model_tag_parser.py +++ b/tests/test_ai_model_tag_parser.py @@ -49,6 +49,25 @@ def test_partial_tags(self, parser): assert parser.text_to_stream_chunk("o") == "" # Output partial non-ignored tag assert parser.text_to_stream_chunk("ther>") == "" # Complete non-ignored tag + def test_writing_tags(self, parser): + parser = TagParser(write_tags=['write']) + # Test partial tag handling + assert parser.text_to_stream_chunk("Simple") == "" + assert parser.text_to_stream_chunk("") == "" # Complete ignored tag + assert parser.text_to_stream_chunk("now") == "now" # Complete ignored tag + assert parser.text_to_stream_chunk(" ") == " " # Content in ignored tag + assert parser.text_to_stream_chunk("<") == "" # Close ignored tag + assert parser.text_to_stream_chunk("/") == "" + assert parser.text_to_stream_chunk("write>") == "" # Close ignored tag + assert parser.text_to_stream_chunk("after") == "" # Content after ignored tag + + # Test partial non-ignored tag + assert parser.text_to_stream_chunk("hey ") == "hey " # Output partial non-ignored tag + assert parser.text_to_stream_chunk("<") == "" # Output partial non-ignored tag + assert parser.text_to_stream_chunk("o") == "" # Output partial non-ignored tag + assert parser.text_to_stream_chunk("ther>") == "" # Complete non-ignored tag + class TestAIModelTagStreaming: @pytest.fixture From 980f9a405555be355cd84d6729a397ea75d8540b Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 22 May 2025 15:08:32 -0700 Subject: [PATCH 29/34] updated lib --- lamoom/ai_models/ai_model.py | 6 ++---- tests/test_ai_model_tag_parser.py | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 20ea85a..3bf8221 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -44,15 +44,13 @@ def reset(self): 'ignored_tag': None, 'writing_tag': None, 'tags': [], + 'in_ignored_tag': False, + 'in_writing_tag': True, } if self.writing_tags: self.state['in_writing_tag'] = False - else: - self.state['in_writing_tag'] = True if self.ignore_tags: self.state['in_ignored_tag'] = False - else: - self.state['in_ignored_tag'] = True def parse_tags(self, chunk: str) -> t.List[str]: matches = [] diff --git a/tests/test_ai_model_tag_parser.py b/tests/test_ai_model_tag_parser.py index 5a58d11..348735d 100644 --- a/tests/test_ai_model_tag_parser.py +++ b/tests/test_ai_model_tag_parser.py @@ -50,7 +50,7 @@ def test_partial_tags(self, parser): assert parser.text_to_stream_chunk("ther>") == "" # Complete non-ignored tag def test_writing_tags(self, parser): - parser = TagParser(write_tags=['write']) + parser = TagParser(writing_tags=['write']) # Test partial tag handling assert parser.text_to_stream_chunk("Simple") == "" assert parser.text_to_stream_chunk("") == "" # Complete non-ignored tag + assert parser.text_to_stream_chunk("ther>") == "" # Complete non-ignored tag class TestAIModelTagStreaming: From fb5f7e277d47624b50c17f047ec2f396842f6260 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 22 May 2025 15:51:57 -0700 Subject: [PATCH 30/34] publish prerelease --- pyproject.toml | 2 +- tests/test_ai_model_tag_parser.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4926bd4..3736844 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a16" +version = "0.1.40a17" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" diff --git a/tests/test_ai_model_tag_parser.py b/tests/test_ai_model_tag_parser.py index 348735d..b0396a0 100644 --- a/tests/test_ai_model_tag_parser.py +++ b/tests/test_ai_model_tag_parser.py @@ -4,6 +4,7 @@ from lamoom.responses import StreamingResponse from lamoom.ai_models.tools.base_tool import TOOL_CALL_START_TAG, TOOL_CALL_END_TAG + class TestTagParser: @pytest.fixture def parser(self): From abc7615ae95039d16d60a8a60b8e9e81059df2c3 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Thu, 22 May 2025 15:52:59 -0700 Subject: [PATCH 31/34] updated lib --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3736844..4149e34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a17" +version = "0.1.40a18" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" From ae5982ae8b3c60e1d9ef60d7e3c9bded1bd01552 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Fri, 23 May 2025 14:30:26 -0700 Subject: [PATCH 32/34] Added streaming_content --- lamoom/ai_models/ai_model.py | 19 ++++++++++--------- lamoom/ai_models/claude/claude_model.py | 13 ++++++++++--- lamoom/ai_models/openai/openai_models.py | 7 +++++-- lamoom/prompt/lamoom.py | 4 ++-- lamoom/responses.py | 1 + pyproject.toml | 2 +- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 3bf8221..0b6a397 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -37,6 +37,9 @@ def __init__(self, ignore_tags: t.List[str] = None, writing_tags: t.List[str] = self.ignore_tags = list(set(ignore_tags or [])) self.writing_tags = list(set(writing_tags or [])) self.reset() + + def is_custom_tags(self): + return bool(self.ignore_tags or self.writing_tags) def reset(self): self.state = { @@ -206,20 +209,15 @@ class AIModel: tiktoken_encoding: t.Optional[str] = "cl100k_base" support_functions: bool = False _provider_name: str = None - stream_ignore_tags: t.List[str] = field(default_factory=list) - _tag_parser: TagParser = field(init=False, default=None) - - def __post_init__(self): - self._tag_parser = TagParser(self.stream_ignore_tags) + _tag_parser: TagParser = field(default=None, init=False) - def _reset_tag_parser(self): - """Reset tag parser state""" - self._tag_parser.reset() + def _init_tag_parser(self, ignore_tags: t.List[str] = None, writing_tags: t.List[str] = None): + self._tag_parser = TagParser(ignore_tags=ignore_tags, writing_tags=writing_tags) def text_to_stream_chunk(self, chunk: str) -> str: """Process incoming chunk of text and return the parsed result.""" if not self._tag_parser: - raise ValueError("Tag parser is not initialized.") + return chunk return self._tag_parser.text_to_stream_chunk(chunk) @property @@ -254,6 +252,8 @@ def call( context: str = '', test_data: dict = {}, client: t.Any = None, + ignore_tags: t.List[str] = None, + writing_tags: t.List[str] = None, **kwargs, ) -> AIResponse: """Common call implementation that handles streaming and tool calls.""" @@ -266,6 +266,7 @@ def call( ) modelname = modelname.replace('/', '_').replace('-', '_') attempts = max_tool_iterations + self._init_tag_parser(ignore_tags=ignore_tags, writing_tags=writing_tags) while attempts > 0: try: stream_response.update_to_another_attempt() diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index 0e2a4c7..b1b4b3d 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -96,9 +96,10 @@ def streaming( content += text_chunk # Only stream content if not in ignored tag - if stream_function: - text_chunk = self.chunk_to_stream(text_chunk) + if stream_function or self._tag_parser.is_custom_tags(): + text_chunk = self.text_to_stream_chunk(text_chunk) if text_chunk and not tool_call_started: + stream_response.streaming_content += text_chunk stream_function(text_chunk, **stream_params) # Check for tool call markers @@ -111,7 +112,13 @@ def streaming( if not tool_call_started: tool_call_started = True continue - + + if stream_function or self._tag_parser.is_custom_tags(): + text_to_stream = self.text_to_stream_chunk('') + if text_to_stream: + stream_function(text_to_stream, **stream_params) + stream_response.streaming_content += text_to_stream + stream_response.content = content stream_response.set_finish_reason(FINISH_REASON_FINISH) return stream_response diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index e3cde51..5e84084 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -116,7 +116,7 @@ def streaming( } if max_tokens: call_kwargs["max_completion_tokens"] = min(max_tokens, self.max_sample_budget) - print(f"Calling OpenAI with params: {call_kwargs}") + logger.info(f"Calling OpenAI with params: {call_kwargs}") completion = client.chat.completions.create(**call_kwargs) for part in completion: if not part.choices: @@ -135,10 +135,12 @@ def streaming( if not delta or (not delta.content and getattr(delta, 'reasoning', None)): continue + if delta.content: - if stream_function: + if stream_function or self._tag_parser.is_custom_tags(): text_to_stream = self.text_to_stream_chunk(delta.content) if text_to_stream: + stream_response.streaming_content += text_to_stream stream_function(text_to_stream, **stream_params) content += delta.content @@ -160,6 +162,7 @@ def streaming( text_to_stream = self.text_to_stream_chunk('') if text_to_stream: stream_function(text_to_stream, **stream_params) + stream_response.streaming_content += text_to_stream stream_response.content = content return stream_response diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index 933e1c0..b7fa675 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -255,7 +255,7 @@ def call( test_data: dict = {}, stream_function: t.Callable = None, check_connection: t.Callable = None, - stream_params: dict = {}, + stream_params: dict = {} ) -> AIResponse: """ Call flow prompt with context and behaviour @@ -279,7 +279,7 @@ def call( messages = calling_messages.get_messages() messages = inject_tool_prompts(messages, list(prompt.tool_registry.values()), calling_context) - print(f'self.clients: {self.clients}, [current_attempt.ai_model.provider_name]: {current_attempt.ai_model.provider_name}') + logger.info(f'self.clients: {self.clients}, [current_attempt.ai_model.provider_name]: {current_attempt.ai_model.provider_name}') for _ in range(0, count_of_retries): try: result = current_attempt.ai_model.call( diff --git a/lamoom/responses.py b/lamoom/responses.py index edf689c..8fe7051 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -63,6 +63,7 @@ class StreamingResponse(AIResponse): started_tmst: int = field(default_factory=current_timestamp_ms) first_stream_tmst: int = None finished_tmst: int = None + streaming_content: str = "" def update_to_another_attempt(self): self.is_detected_tool_call = False diff --git a/pyproject.toml b/pyproject.toml index 4149e34..6ccc958 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a18" +version = "0.1.40a19" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" From f53435f99b08cb2dad1552c27e98eebf1ef1c5e2 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Mon, 16 Jun 2025 10:48:02 -0700 Subject: [PATCH 33/34] fixed saving of the prompt --- .gitignore | 3 ++- Makefile | 6 +----- claude.config | 0 lamoom/ai_models/ai_model.py | 13 ++++++++++--- lamoom/ai_models/claude/claude_model.py | 6 ++++-- lamoom/ai_models/openai/openai_models.py | 10 +++++----- lamoom/prompt/base_prompt.py | 2 ++ lamoom/prompt/lamoom.py | 3 +-- lamoom/responses.py | 5 +++++ pyproject.toml | 2 +- tests/prompts/test_web_call.py | 1 + tests/test_ai_model_tag_parser.py | 4 +++- 12 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 claude.config diff --git a/.gitignore b/.gitignore index bdcbb4f..9e50489 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ dist .vscode .pytest_cache python -.env.test \ No newline at end of file +.env.test +*/logs/ \ No newline at end of file diff --git a/Makefile b/Makefile index 3eb2757..ea4d7c0 100644 --- a/Makefile +++ b/Makefile @@ -25,11 +25,7 @@ lint: poetry run isort --settings-path pyproject.toml --check-only . test: - poetry run pytest --cache-clear -vv tests \ - --cov=${PROJECT_FOLDER} \ - --cov-config=.coveragerc \ - --cov-fail-under=81 \ - --cov-report term-missing + poetry run pytest --cache-clear -vv tests .PHONY: format format: make-black isort-check flake8 make-mypy diff --git a/claude.config b/claude.config new file mode 100644 index 0000000..e69de29 diff --git a/lamoom/ai_models/ai_model.py b/lamoom/ai_models/ai_model.py index 0b6a397..e43b69a 100644 --- a/lamoom/ai_models/ai_model.py +++ b/lamoom/ai_models/ai_model.py @@ -5,7 +5,7 @@ from enum import Enum import logging from _decimal import Decimal - +from lamoom.prompt.base_prompt import BasePrompt import tiktoken from lamoom import settings @@ -262,7 +262,14 @@ def call( # Prepare streaming response stream_response = StreamingResponse( tool_registry=tool_registry, - messages=current_messages + messages=current_messages, + prompt=BasePrompt( + messages=current_messages, + functions=kwargs.get("tools"), + max_tokens=max_tokens, + temperature=kwargs.get("temperature"), + top_p=kwargs.get("top_p"), + ) ) modelname = modelname.replace('/', '_').replace('-', '_') attempts = max_tool_iterations @@ -363,7 +370,7 @@ def calculate_budget_for_text(self, text: str) -> int: return 0 return len(encoding.encode(text)) - def save_call(self, stream_response: StreamingResponse, prompt: "Prompt", context: str, attempt: int=0, test_data: dict = {}, client: t.Any = None): + def save_call(self, stream_response: StreamingResponse, prompt: "Prompt", context: dict, attempt: int=0, test_data: dict = {}, client: t.Any = None): sample_budget = self.calculate_budget_for_text( stream_response.get_message_str() diff --git a/lamoom/ai_models/claude/claude_model.py b/lamoom/ai_models/claude/claude_model.py index b1b4b3d..0669ac7 100644 --- a/lamoom/ai_models/claude/claude_model.py +++ b/lamoom/ai_models/claude/claude_model.py @@ -100,7 +100,8 @@ def streaming( text_chunk = self.text_to_stream_chunk(text_chunk) if text_chunk and not tool_call_started: stream_response.streaming_content += text_chunk - stream_function(text_chunk, **stream_params) + if stream_function: + stream_function(text_chunk, **stream_params) # Check for tool call markers if tool_call_started and TOOL_CALL_END_TAG in content: @@ -116,7 +117,8 @@ def streaming( if stream_function or self._tag_parser.is_custom_tags(): text_to_stream = self.text_to_stream_chunk('') if text_to_stream: - stream_function(text_to_stream, **stream_params) + if stream_function: + stream_function(text_to_stream, **stream_params) stream_response.streaming_content += text_to_stream stream_response.content = content diff --git a/lamoom/ai_models/openai/openai_models.py b/lamoom/ai_models/openai/openai_models.py index 5e84084..3c60d50 100644 --- a/lamoom/ai_models/openai/openai_models.py +++ b/lamoom/ai_models/openai/openai_models.py @@ -135,16 +135,15 @@ def streaming( if not delta or (not delta.content and getattr(delta, 'reasoning', None)): continue - if delta.content: + content += delta.content if stream_function or self._tag_parser.is_custom_tags(): text_to_stream = self.text_to_stream_chunk(delta.content) if text_to_stream: stream_response.streaming_content += text_to_stream - stream_function(text_to_stream, **stream_params) - content += delta.content + if stream_function: + stream_function(text_to_stream, **stream_params) - if getattr(delta, 'reasoning', None) and delta.reasoning: logger.debug(f'Adding reasoning {delta.reasoning}') stream_response.reasoning += delta.reasoning @@ -161,8 +160,9 @@ def streaming( if stream_function: text_to_stream = self.text_to_stream_chunk('') if text_to_stream: - stream_function(text_to_stream, **stream_params) stream_response.streaming_content += text_to_stream + if stream_function: + stream_function(text_to_stream, **stream_params) stream_response.content = content return stream_response diff --git a/lamoom/prompt/base_prompt.py b/lamoom/prompt/base_prompt.py index ecb0ab1..cd85d9f 100644 --- a/lamoom/prompt/base_prompt.py +++ b/lamoom/prompt/base_prompt.py @@ -17,6 +17,8 @@ class BasePrompt: chats: t.List[ChatsEntity] = field(default_factory=list) pipe: t.List[str] = field(default_factory=list) functions: t.List[dict] = None + messages: t.List[dict] = field(default_factory=list) + max_tokens: int = 0 top_p: float = 0.0 temperature: float = 0.0 # Add tool registry diff --git a/lamoom/prompt/lamoom.py b/lamoom/prompt/lamoom.py index b7fa675..a3649c6 100644 --- a/lamoom/prompt/lamoom.py +++ b/lamoom/prompt/lamoom.py @@ -278,7 +278,6 @@ def call( calling_messages = user_prompt.resolve(calling_context, prompt.tool_registry) messages = calling_messages.get_messages() messages = inject_tool_prompts(messages, list(prompt.tool_registry.values()), calling_context) - logger.info(f'self.clients: {self.clients}, [current_attempt.ai_model.provider_name]: {current_attempt.ai_model.provider_name}') for _ in range(0, count_of_retries): try: @@ -292,7 +291,7 @@ def call( client_secrets=self.clients[current_attempt.ai_model.provider_name], modelname=model, prompt=prompt, - context=json.dumps(context), + context=context, test_data=test_data, client=self, **params, diff --git a/lamoom/responses.py b/lamoom/responses.py index 8fe7051..95d74b4 100644 --- a/lamoom/responses.py +++ b/lamoom/responses.py @@ -52,6 +52,11 @@ def response(self) -> str: def get_message_str(self) -> str: return self.response + @property + def parsed_json(self) -> t.Optional[dict]: + parsed_json_response = get_json_from_response(self) + return parsed_json_response.parsed_content if parsed_json_response else None + @dataclass(kw_only=True) class StreamingResponse(AIResponse): diff --git a/pyproject.toml b/pyproject.toml index 6ccc958..3057612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40a19" +version = "0.1.40" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md" diff --git a/tests/prompts/test_web_call.py b/tests/prompts/test_web_call.py index 57c7d30..6e60978 100644 --- a/tests/prompts/test_web_call.py +++ b/tests/prompts/test_web_call.py @@ -30,6 +30,7 @@ def test_web_call(client): # initial version of the prompt prompt_id = 'test-web-search' prompt = Prompt(id=prompt_id) + prompt.add("", role='system') prompt.add("{text}", role='user') prompt.add_tool(WEB_SEARCH_TOOL) diff --git a/tests/test_ai_model_tag_parser.py b/tests/test_ai_model_tag_parser.py index b0396a0..caec92d 100644 --- a/tests/test_ai_model_tag_parser.py +++ b/tests/test_ai_model_tag_parser.py @@ -73,7 +73,9 @@ def test_writing_tags(self, parser): class TestAIModelTagStreaming: @pytest.fixture def model(self): - return AIModel(stream_ignore_tags=['think', 'code']) + model = AIModel() + model._init_tag_parser(ignore_tags=['think', 'reason'], writing_tags=[]) + return model @pytest.fixture def mock_stream_function(self): From 3c9a7eab42cf0535ce783ea77d313330bd17c333 Mon Sep 17 00:00:00 2001 From: Kate Yanchenko Date: Mon, 16 Jun 2025 10:59:01 -0700 Subject: [PATCH 34/34] updated version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3057612..4b19659 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "lamoom" -version = "0.1.40" +version = "0.1.42" description = "" authors = ["Lamoom Engineering Team "] readme = "README.md"