From f16b865d7b523f59581e7d2679226a4f0f49d0e2 Mon Sep 17 00:00:00 2001 From: Mansur Uralov Date: Tue, 4 Feb 2025 08:40:09 +0100 Subject: [PATCH 1/5] Add retry mechanism for subtask generation - Add tenacity library for retry logic in planner invocation - Return erorr message in error field - Create custom SubtasksMissingError exception for handling missing subtasks - Update supervisor agent to use retry decorator with exponential backoff --- poetry.lock | 2 +- pyproject.toml | 4 +++ src/agents/common/exceptions.py | 6 ++++ src/agents/supervisor/agent.py | 28 +++++++++++-------- src/services/conversation.py | 1 - .../supervisor/test_supervisor_agent.py | 18 ++---------- 6 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 src/agents/common/exceptions.py diff --git a/poetry.lock b/poetry.lock index 7b11f907..ef1f4785 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6166,4 +6166,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "~3.12" -content-hash = "9e2205ccfcf513cc6a7adef24999a4985171c0157f14e73e17333b81ebca3d9c" +content-hash = "de5faafa8fb2b356355ee4e406f31ebefdc03891f4e706a9949bfd610f1bf879" diff --git a/pyproject.toml b/pyproject.toml index a2143c0b..d0026714 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ python-decouple = "^3.8" redis = "^5.0.8" requests = "^2.32.3" scrubadub = {extras = ["all"], version = "^2.0.1"} +tenacity = "^9.0.0" tiktoken = "^0.7.0" [tool.poetry.group.test.dependencies] @@ -68,6 +69,9 @@ pythonpath = [ testpaths = [ "tests", ] +env_files = [ + ".env.test" +] [tool.poe.tasks] lint = "ruff check ." diff --git a/src/agents/common/exceptions.py b/src/agents/common/exceptions.py new file mode 100644 index 00000000..bfa402b1 --- /dev/null +++ b/src/agents/common/exceptions.py @@ -0,0 +1,6 @@ +class SubtasksMissingError(Exception): + """Exception raised when no subtasks are created for the given query.""" + + def __init__(self, query: str): + self.query = query + super().__init__(f"Subtasks are missing for the given query: {query}") diff --git a/src/agents/supervisor/agent.py b/src/agents/supervisor/agent.py index 427c1467..eb3071ba 100644 --- a/src/agents/supervisor/agent.py +++ b/src/agents/supervisor/agent.py @@ -1,3 +1,4 @@ +import logging from typing import Any, Literal, cast from langchain_core.embeddings import Embeddings @@ -9,9 +10,11 @@ from langgraph.graph import StateGraph from langgraph.graph.graph import CompiledGraph from pydantic import BaseModel, Field +from tenacity import after_log, before_log, retry, stop_after_attempt, wait_exponential from agents.common.constants import ( COMMON, + ERROR, FINALIZER, K8S_AGENT, KYMA_AGENT, @@ -19,6 +22,7 @@ NEXT, PLANNER, ) +from agents.common.exceptions import SubtasksMissingError from agents.common.response_converter import IResponseConverter, ResponseConverter from agents.common.state import Plan from agents.common.utils import create_node_output, filter_messages @@ -145,8 +149,15 @@ def _create_planner_chain(self, model: IModel) -> RunnableSequence: ) return self.planner_prompt | model.llm.with_structured_output(Plan) # type: ignore + @retry( + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=4), + reraise=True, + before=before_log(logger, logging.WARNING), + after=after_log(logger, logging.DEBUG), + ) async def _invoke_planner(self, state: SupervisorState) -> Plan: - """Invoke the planner.""" + """Invoke the planner with retry logic using tenacity.""" filtered_messages = filter_messages_via_checks( state.messages, @@ -187,23 +198,16 @@ async def _plan(self, state: SupervisorState) -> dict[str, Any]: # if the Planner did not respond directly but also failed to create any subtasks, raise an exception if not plan.subtasks: - raise Exception( - f"No subtasks are created for the given query: {state.messages[-1].content}" - ) + raise SubtasksMissingError(str(state.messages[-1].content)) # return the plan with the subtasks to be dispatched by the Router return create_node_output( next=ROUTER, subtasks=plan.subtasks, ) - except Exception as e: - logger.error(f"Error in planning: {e}") + except Exception: + logger.exception("Error in planning") return { - MESSAGES: [ - AIMessage( - content=f"Sorry, I encountered an error while processing the request. Error: {e}", - name=PLANNER, - ) - ] + ERROR: "Unexpected error while processing the request. Please try again later.", } def _final_response_chain(self, state: SupervisorState) -> RunnableSequence: diff --git a/src/services/conversation.py b/src/services/conversation.py index 29a8368a..376d43da 100644 --- a/src/services/conversation.py +++ b/src/services/conversation.py @@ -133,5 +133,4 @@ async def handle_request( async for chunk in self._companion_graph.astream( conversation_id, message, k8s_client ): - logger.debug(f"Sending chunk: {chunk}") yield chunk.encode() diff --git a/tests/unit/agents/supervisor/test_supervisor_agent.py b/tests/unit/agents/supervisor/test_supervisor_agent.py index 568b2866..710373d2 100644 --- a/tests/unit/agents/supervisor/test_supervisor_agent.py +++ b/tests/unit/agents/supervisor/test_supervisor_agent.py @@ -5,7 +5,7 @@ from langchain_core.messages import AIMessage, HumanMessage from langgraph.constants import END -from agents.common.constants import COMMON, PLANNER +from agents.common.constants import COMMON, ERROR, PLANNER from agents.common.state import CompanionState, Plan, SubTask from agents.k8s.agent import K8S_AGENT from agents.kyma.agent import KYMA_AGENT @@ -273,13 +273,7 @@ async def test_agent_generate_final_response( "What is a Kubernetes pod?", '{"response":null, "subtasks": null}', { - "messages": [ - AIMessage( - content="Sorry, I encountered an error while processing the request. " - "Error: No subtasks are created for the given query: What is a Kubernetes pod?", - name=PLANNER, - ) - ] + ERROR: "Unexpected error while processing the request. Please try again later.", }, None, ), @@ -305,13 +299,7 @@ async def test_agent_generate_final_response( "What is a Kubernetes service?", '{"response":null,"subtasks": [{"description": "Explain Kubernetes service", "assigned_to": "KubernetesAgent","status" : "pending"}]}', { - "messages": [ - AIMessage( - content="Sorry, I encountered an error while processing the request. " - "Error: fake error", - name=PLANNER, - ) - ] + ERROR: "Unexpected error while processing the request. Please try again later.", }, "fake error", ), From b992298811280899053247afe64b7b681b0b8ee5 Mon Sep 17 00:00:00 2001 From: Mansur Uralov Date: Tue, 4 Feb 2025 13:05:41 +0100 Subject: [PATCH 2/5] Add unit test cases for planner retry mechanism - Implement table-driven test for _invoke_planner retry behavior --- .../supervisor/test_supervisor_agent.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/unit/agents/supervisor/test_supervisor_agent.py b/tests/unit/agents/supervisor/test_supervisor_agent.py index 710373d2..00897510 100644 --- a/tests/unit/agents/supervisor/test_supervisor_agent.py +++ b/tests/unit/agents/supervisor/test_supervisor_agent.py @@ -328,3 +328,89 @@ async def test_agent_plan( assert result == expected_output mock_invoke_planner.assert_called_once_with(state) + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "description, side_effect, expected_calls, expected_result, expected_error", + [ + ( + "Success after two failures", + [ + ValueError("Attempt 1 failed"), + ValueError("Attempt 2 failed"), + Plan( + response=None, + subtasks=[ + SubTask(description="Test task 1", assigned_to=K8S_AGENT) + ], + ), + ], + 3, + Plan( + response=None, + subtasks=[ + SubTask(description="Test task 1", assigned_to=K8S_AGENT) + ], + ), + None, + ), + ( + "Immediate success without retries", + Plan( + response=None, + subtasks=[ + SubTask(description="Test task 2", assigned_to=K8S_AGENT) + ], + ), + 1, + Plan( + response=None, + subtasks=[ + SubTask(description="Test task 2", assigned_to=K8S_AGENT) + ], + ), + None, + ), + ( + "Different error types", + [ + ValueError("Value error"), + RuntimeError("Runtime error"), + ValueError("Connection error"), + ], + 3, + None, + ValueError("Connection error"), + ), + ], + ) + async def test_invoke_planner_retry_behavior( + self, + supervisor_agent, + description, + side_effect, + expected_calls, + expected_result, + expected_error, + ): + """Table-driven test for _invoke_planner retry behavior covering multiple scenarios.""" + state = SupervisorState(messages=[HumanMessage(content="Test query")]) + mock_chain = AsyncMock() + + # Configure mock to return/raise based on side_effect + if isinstance(side_effect, list): + mock_chain.ainvoke.side_effect = side_effect + else: + mock_chain.ainvoke.return_value = side_effect + + with patch.object(supervisor_agent, "_planner_chain", mock_chain): + if expected_error: + with pytest.raises(type(expected_error)) as exc_info: + await supervisor_agent._invoke_planner(state) + assert str(expected_error) in str(exc_info.value) + else: + result = await supervisor_agent._invoke_planner(state) + assert result == expected_result + + # Verify the number of calls + assert mock_chain.ainvoke.call_count == expected_calls From 3e66c15a0da000f8b22a7ddd0acba0f751c6daf9 Mon Sep 17 00:00:00 2001 From: Mansur Uralov Date: Tue, 4 Feb 2025 17:13:58 +0100 Subject: [PATCH 3/5] Add ainvoke_chain utility for consistent chain invocation with retry logic - Create new utils/chain.py module with ainvoke_chain function - Implement retry mechanism for chain invocations using tenacity - Replace direct chain.ainvoke calls across multiple modules with ainvoke_chain - Add logging and error handling for chain invocations - Standardize chain invocation with configurable retry strategy --- src/agents/common/agent.py | 18 +++++- src/agents/graph.py | 4 +- src/agents/summarization/summarization.py | 4 +- src/agents/supervisor/agent.py | 64 ++++++++++----------- src/rag/generator.py | 6 +- src/rag/query_generator.py | 6 +- src/rag/reranker/reranker.py | 6 +- src/utils/chain.py | 68 +++++++++++++++++++++++ 8 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 src/utils/chain.py diff --git a/src/agents/common/agent.py b/src/agents/common/agent.py index 91835128..779cc209 100644 --- a/src/agents/common/agent.py +++ b/src/agents/common/agent.py @@ -12,6 +12,7 @@ from agents.common.constants import ( AGENT_MESSAGES, AGENT_MESSAGES_SUMMARY, + ERROR, IS_LAST_STEP, MESSAGES, MY_TASK, @@ -20,12 +21,16 @@ from agents.common.state import BaseAgentState, SubTaskStatus from agents.common.utils import filter_messages from agents.summarization.summarization import Summarization +from utils.chain import ainvoke_chain +from utils.logging import get_logger from utils.models.factory import IModel, ModelType from utils.settings import ( SUMMARIZATION_TOKEN_LOWER_LIMIT, SUMMARIZATION_TOKEN_UPPER_LIMIT, ) +logger = get_logger(__name__) + def subtask_selector_edge(state: BaseAgentState) -> Literal["agent", "finalizer"]: """Function that determines whether to finalize or call agent.""" @@ -126,7 +131,11 @@ async def _invoke_chain(self, state: BaseAgentState, config: RunnableConfig) -> if len(state.agent_messages) == 0: inputs[AGENT_MESSAGES] = filter_messages(state.messages) - response = await self.chain.ainvoke(inputs, config) + response = await ainvoke_chain( + self.chain, + inputs, + config=config, + ) return response async def _model_node( @@ -135,13 +144,16 @@ async def _model_node( try: response = await self._invoke_chain(state, config) except Exception as e: + error_message = f"An error occurred while processing the request: {e}" + logger.error(error_message) return { AGENT_MESSAGES: [ AIMessage( - content=f"Sorry, I encountered an error while processing the request. Error: {e}", + content=f"Sorry, {error_message}", name=self.name, ) - ] + ], + ERROR: error_message, } # if the recursive limit is reached and the response is a tool call, return a message. diff --git a/src/agents/graph.py b/src/agents/graph.py index c27dbc9a..968695ed 100644 --- a/src/agents/graph.py +++ b/src/agents/graph.py @@ -38,6 +38,7 @@ from agents.summarization.summarization import Summarization from agents.supervisor.agent import SUPERVISOR, SupervisorAgent from services.k8s import IK8sClient +from utils.chain import ainvoke_chain from utils.logging import get_logger from utils.models.factory import IModel, ModelType from utils.settings import ( @@ -147,7 +148,8 @@ def _create_common_chain(model: IModel) -> RunnableSequence: async def _invoke_common_node(self, state: CompanionState, subtask: str) -> str: """Invoke the common node.""" - response = await self._common_chain.ainvoke( + response = await ainvoke_chain( + self._common_chain, { "messages": state.get_messages_including_summary(), "query": subtask, diff --git a/src/agents/summarization/summarization.py b/src/agents/summarization/summarization.py index d10104a2..c2bfd83e 100644 --- a/src/agents/summarization/summarization.py +++ b/src/agents/summarization/summarization.py @@ -15,6 +15,7 @@ from agents.common.utils import compute_messages_token_count, compute_string_token_count from agents.summarization.prompts import MESSAGES_SUMMARIZATION_PROMPT +from utils.chain import ainvoke_chain from utils.models.factory import IModel, ModelType @@ -88,7 +89,8 @@ async def get_summary( if len(messages) == 0: return "" - res = await self._chain.ainvoke( + res = await ainvoke_chain( + self._chain, {"messages": messages}, config=config, ) diff --git a/src/agents/supervisor/agent.py b/src/agents/supervisor/agent.py index eb3071ba..640528ad 100644 --- a/src/agents/supervisor/agent.py +++ b/src/agents/supervisor/agent.py @@ -1,4 +1,3 @@ -import logging from typing import Any, Literal, cast from langchain_core.embeddings import Embeddings @@ -10,7 +9,6 @@ from langgraph.graph import StateGraph from langgraph.graph.graph import CompiledGraph from pydantic import BaseModel, Field -from tenacity import after_log, before_log, retry, stop_after_attempt, wait_exponential from agents.common.constants import ( COMMON, @@ -33,6 +31,7 @@ PLANNER_SYSTEM_PROMPT, ) from agents.supervisor.state import SupervisorState +from utils.chain import ainvoke_chain from utils.filter_messages import ( filter_messages_via_checks, is_ai_message, @@ -149,13 +148,6 @@ def _create_planner_chain(self, model: IModel) -> RunnableSequence: ) return self.planner_prompt | model.llm.with_structured_output(Plan) # type: ignore - @retry( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=1, max=4), - reraise=True, - before=before_log(logger, logging.WARNING), - after=after_log(logger, logging.DEBUG), - ) async def _invoke_planner(self, state: SupervisorState) -> Plan: """Invoke the planner with retry logic using tenacity.""" @@ -170,8 +162,9 @@ async def _invoke_planner(self, state: SupervisorState) -> Plan: ) reduces_messages = filter_messages(filtered_messages) - plan: Plan = await self._planner_chain.ainvoke( - input={ + plan: Plan = await ainvoke_chain( + self._planner_chain, + { "messages": reduces_messages, }, ) @@ -230,40 +223,39 @@ async def _generate_final_response(self, state: SupervisorState) -> dict[str, An final_response_chain = self._final_response_chain(state) - try: - final_response = await final_response_chain.ainvoke( - {"messages": state.messages}, - ) + final_response = await ainvoke_chain( + final_response_chain, + {"messages": state.messages}, + ) + return { + MESSAGES: [ + AIMessage( + content=final_response.content, + name=FINALIZER, + ) + ], + NEXT: END, + } + + async def _get_converted_final_response( + self, state: SupervisorState + ) -> dict[str, Any]: + """Convert the generated final response.""" + try: + final_response = await self._generate_final_response(state) + return self.response_converter.convert_final_response(final_response) + except Exception: + logger.exception("Error in generating final response") return { MESSAGES: [ AIMessage( - content=final_response.content, - name=FINALIZER, - ) - ], - NEXT: END, - } - except Exception as e: - logger.error(f"Error in generating final response: {e}") - return { - MESSAGES: [ - AIMessage( - content=f"Sorry, I encountered an error while processing the request. Error: {e}", + content="Sorry, I encountered an error while processing the request. Try again later.", name=FINALIZER, ) ] } - async def _get_converted_final_response( - self, state: SupervisorState - ) -> dict[str, Any]: - """Convert the generated final response.""" - - final_response = await self._generate_final_response(state) - - return self.response_converter.convert_final_response(final_response) - def _build_graph(self) -> CompiledGraph: # Define a new graph. workflow = StateGraph(SupervisorState) diff --git a/src/rag/generator.py b/src/rag/generator.py index fbdb430b..53ab3c77 100644 --- a/src/rag/generator.py +++ b/src/rag/generator.py @@ -5,6 +5,7 @@ from langchain_core.prompts import PromptTemplate from rag.prompts import GENERATOR_PROMPT +from utils.chain import ainvoke_chain from utils.logging import get_logger from utils.models.factory import IModel @@ -33,8 +34,9 @@ async def agenerate(self, relevant_docs: list[Document], query: str) -> str: # Convert Document objects to a list of their page_content docs_content = "\n\n".join(doc.page_content for doc in relevant_docs) try: - response = await self.rag_chain.ainvoke( - {"context": docs_content, "query": query} + response = await ainvoke_chain( + self.rag_chain, + {"context": docs_content, "query": query}, ) except Exception as e: logger.exception(f"Error generating response for query: {query}") diff --git a/src/rag/query_generator.py b/src/rag/query_generator.py index af21242c..a5b85435 100644 --- a/src/rag/query_generator.py +++ b/src/rag/query_generator.py @@ -8,6 +8,7 @@ QUERY_GENERATOR_PROMPT_TEMPLATE, ) from utils import logging +from utils.chain import ainvoke_chain from utils.models.factory import IModel logger = logging.get_logger(__name__) @@ -57,7 +58,10 @@ def _create_chain(self) -> Any: async def agenerate_queries(self, query: str) -> Queries: """Generate multiple queries based on the input query.""" try: - queries = await self._chain.ainvoke({"query": query}) + queries = await ainvoke_chain( + self._chain, + {"query": query}, + ) return cast(Queries, queries) except Exception: logger.exception( diff --git a/src/rag/reranker/reranker.py b/src/rag/reranker/reranker.py index 49e6884c..710bc0cf 100644 --- a/src/rag/reranker/reranker.py +++ b/src/rag/reranker/reranker.py @@ -7,6 +7,7 @@ from rag.reranker.prompt import RERANKER_PROMPT_TEMPLATE from rag.reranker.rrf import get_relevant_documents from rag.reranker.utils import document_to_str +from utils.chain import ainvoke_chain from utils.logging import get_logger from utils.models.factory import IModel @@ -92,12 +93,13 @@ async def _chain_ainvoke( """ # reranking using the LLM model - response: RerankedDocs = await self.chain.ainvoke( + response: RerankedDocs = await ainvoke_chain( + self.chain, { "documents": format_documents(docs), "queries": format_queries(queries), "limit": limit, - } + }, ) # return reranked documents capped at the output limit reranked_docs = [ diff --git a/src/utils/chain.py b/src/utils/chain.py new file mode 100644 index 00000000..88685740 --- /dev/null +++ b/src/utils/chain.py @@ -0,0 +1,68 @@ +import logging +from typing import Any + +from langchain.chains.base import Chain +from tenacity import ( + RetryCallState, + retry, + stop_after_attempt, + wait_incrementing, +) + +logger = logging.getLogger(__name__) + + +def after_log(retry_state: RetryCallState) -> None: + """Log retry attempts with appropriate log levels. + + Args: + retry_state (RetryCallState): Current state of the retry operation + """ + loglevel = logging.INFO if retry_state.attempt_number < 1 else logging.WARNING + logger.log( + loglevel, + "Retrying %s: attempt %s", + f"{retry_state.fn.__module__}.{retry_state.fn.__name__}", + retry_state.attempt_number, + ) + + +@retry( + stop=stop_after_attempt(3), + wait=wait_incrementing(start=2, increment=3), + after=after_log, + reraise=True, +) +async def ainvoke_chain( + chain: Chain, + inputs: dict[str, Any] | Any, + *, + config: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Invokes a LangChain chain asynchronously. + Retries the LLM calls if they fail with the provided wait strategy. + Tries 3 times, waits 2 seconds between attempts, i.e. 2, 5. + Logs warnings and raises an error. + + Args: + chain (Chain): The LangChain chain to invoke + inputs (Union[Dict[str, Any], Any]): Input parameters for the chain. Can be either a dictionary + of inputs or a single value that will be wrapped in a dict with key "input" + config (Optional[Dict[str, Any]], optional): Additional configuration for chain execution. + Defaults to None. + + Returns: + Dict[str, Any]: The chain execution results + """ + # Convert single value input to dict if needed + chain_inputs = inputs if isinstance(inputs, dict) else {"input": inputs} + + logger.debug(f"Invoking chain with inputs: {chain_inputs}") + + result = await chain.ainvoke( + input=chain_inputs, + config=config, + ) + + logger.debug(f"Chain execution completed. Result: {result}") + return result From a2a2ef9bc3479e631cc35f67fed67e3a09828287 Mon Sep 17 00:00:00 2001 From: Mansur Uralov Date: Wed, 5 Feb 2025 08:26:31 +0100 Subject: [PATCH 4/5] Add/Update unit tests for agent and supervisor modules - Add unit test for shared chain invocation function - Update test assertions for chain invocation with config parameter - Remove redundant test cases in supervisor agent --- tests/unit/agents/common/test_agent.py | 54 +++++--- .../supervisor/test_supervisor_agent.py | 127 ++---------------- tests/unit/rag/reranker/test_reranker.py | 5 +- tests/unit/rag/test_generator.py | 4 +- tests/unit/rag/test_query_generator.py | 2 +- tests/unit/utils/test_chain.py | 108 +++++++++++++++ 6 files changed, 162 insertions(+), 138 deletions(-) create mode 100644 tests/unit/utils/test_chain.py diff --git a/tests/unit/agents/common/test_agent.py b/tests/unit/agents/common/test_agent.py index 0785af88..e69347b4 100644 --- a/tests/unit/agents/common/test_agent.py +++ b/tests/unit/agents/common/test_agent.py @@ -6,6 +6,7 @@ AIMessage, BaseMessage, HumanMessage, + SystemMessage, ToolMessage, ) from langchain_core.prompts import ( @@ -18,7 +19,7 @@ from langgraph.graph.graph import CompiledGraph from agents.common.agent import BaseAgent -from agents.common.constants import AGENT_MESSAGES +from agents.common.constants import AGENT_MESSAGES, ERROR from agents.common.state import BaseAgentState, SubTask, SubTaskStatus from agents.k8s.tools.logs import fetch_pod_logs_tool from agents.k8s.tools.query import k8s_query_tool @@ -144,26 +145,40 @@ def test_create_chain(self, mock_models): agent_messages=[], subtasks=[], k8s_client=Mock(spec=IK8sClient), - my_task=SubTask(description="test", assigned_to="KubernetesAgent"), + my_task=SubTask( + description="test 1", assigned_to="KubernetesAgent" + ), ), { - "agent_messages": [HumanMessage(content="What is K8s?")], - "query": "test", + "agent_messages": [ + HumanMessage( + content="What is K8s?", + ) + ], + "query": "test 1", }, ), - # Test case when agent_messages is non-empty, should not use from messages field. + # Test case when agent_messages is non-empty, should use get_agent_messages_including_summary ( TestAgentState( is_last_step=False, messages=[HumanMessage(content="What is K8s?")], - agent_messages=[HumanMessage(content="What is an agent?")], + agent_messages=[HumanMessage(content="What is deployment?")], + agent_messages_summary="Summary of previous messages: K8s is orchestration tool. Deployment is workload resource.", subtasks=[], k8s_client=Mock(spec=IK8sClient), - my_task=SubTask(description="test", assigned_to="KubernetesAgent"), + my_task=SubTask( + description="test 2", assigned_to="KubernetesAgent" + ), ), { - "agent_messages": [HumanMessage(content="What is an agent?")], - "query": "test", + "agent_messages": [ + SystemMessage( + content="Summary of previous messages: K8s is orchestration tool. Deployment is workload resource.", + ), + HumanMessage(content="What is deployment?"), + ], + "query": "test 2", }, ), ], @@ -176,11 +191,20 @@ async def test_invoke_chain( agent.chain = Mock() agent.chain.ainvoke = AsyncMock() - # When - _ = await agent._invoke_chain(given_state, {}) + await agent._invoke_chain(given_state, {}) # Then - agent.chain.ainvoke.assert_called_once_with(expected_inputs, {}) + # Get the actual call arguments + assert agent.chain.ainvoke.call_count == 1 + actual_call = agent.chain.ainvoke.call_args + actual_input = actual_call.kwargs["input"] + + # Remove id field from messages for comparison as it is not deterministic + actual_messages = actual_input.get("agent_messages", []) + expected_messages = expected_inputs.get("agent_messages", []) + for msg in actual_messages + expected_messages: + msg.id = None + assert actual_input == expected_inputs def test_build_graph(self, mock_models): # Given @@ -341,11 +365,11 @@ def test_subtask_selector_node( { AGENT_MESSAGES: [ AIMessage( - content="Sorry, I encountered an error while processing the request. " - "Error: This is a dummy exception from model.", + content="Sorry, An error occurred while processing the request: This is a dummy exception from model.", name="KubernetesAgent", ) - ] + ], + ERROR: "An error occurred while processing the request: This is a dummy exception from model.", }, { AGENT_MESSAGES: [AIMessage(content="dummy message 1")], diff --git a/tests/unit/agents/supervisor/test_supervisor_agent.py b/tests/unit/agents/supervisor/test_supervisor_agent.py index 00897510..f06d7a26 100644 --- a/tests/unit/agents/supervisor/test_supervisor_agent.py +++ b/tests/unit/agents/supervisor/test_supervisor_agent.py @@ -170,28 +170,6 @@ def test_agent_route( }, None, ), - ( - "Handles exception during final response generation", - "What is Kubernetes?", - [ - HumanMessage(content="What is Kubernetes?"), - AIMessage( - content="Kubernetes is a container orchestration platform.", - name="KubernetesAgent", - ), - ], - None, - { - "messages": [ - AIMessage( - content="Sorry, I encountered an error while processing the request. " - "Error: Error in finalizer node: Test error", - name="Finalizer", - ) - ] - }, - "Error in finalizer node: Test error", - ), ], ) async def test_agent_generate_final_response( @@ -204,27 +182,26 @@ async def test_agent_generate_final_response( expected_output, expected_error, ): + # Given state = SupervisorState(messages=conversation_messages) mock_final_response_chain = AsyncMock() - if expected_error: - mock_final_response_chain.ainvoke.side_effect = Exception(expected_error) - else: - mock_final_response_chain.ainvoke.return_value.content = ( - final_response_content - ) + mock_final_response_chain.ainvoke.return_value.content = final_response_content with patch.object( supervisor_agent, "_final_response_chain", return_value=mock_final_response_chain, ): + # When result = await supervisor_agent._generate_final_response(state) - assert result == expected_output - mock_final_response_chain.ainvoke.assert_called_once_with( - {"messages": conversation_messages} - ) + # Then + assert result == expected_output + + mock_final_response_chain.ainvoke.assert_called_once_with( + config=None, input={"messages": conversation_messages} + ) @pytest.mark.asyncio @pytest.mark.parametrize( @@ -328,89 +305,3 @@ async def test_agent_plan( assert result == expected_output mock_invoke_planner.assert_called_once_with(state) - - @pytest.mark.asyncio - @pytest.mark.parametrize( - "description, side_effect, expected_calls, expected_result, expected_error", - [ - ( - "Success after two failures", - [ - ValueError("Attempt 1 failed"), - ValueError("Attempt 2 failed"), - Plan( - response=None, - subtasks=[ - SubTask(description="Test task 1", assigned_to=K8S_AGENT) - ], - ), - ], - 3, - Plan( - response=None, - subtasks=[ - SubTask(description="Test task 1", assigned_to=K8S_AGENT) - ], - ), - None, - ), - ( - "Immediate success without retries", - Plan( - response=None, - subtasks=[ - SubTask(description="Test task 2", assigned_to=K8S_AGENT) - ], - ), - 1, - Plan( - response=None, - subtasks=[ - SubTask(description="Test task 2", assigned_to=K8S_AGENT) - ], - ), - None, - ), - ( - "Different error types", - [ - ValueError("Value error"), - RuntimeError("Runtime error"), - ValueError("Connection error"), - ], - 3, - None, - ValueError("Connection error"), - ), - ], - ) - async def test_invoke_planner_retry_behavior( - self, - supervisor_agent, - description, - side_effect, - expected_calls, - expected_result, - expected_error, - ): - """Table-driven test for _invoke_planner retry behavior covering multiple scenarios.""" - state = SupervisorState(messages=[HumanMessage(content="Test query")]) - mock_chain = AsyncMock() - - # Configure mock to return/raise based on side_effect - if isinstance(side_effect, list): - mock_chain.ainvoke.side_effect = side_effect - else: - mock_chain.ainvoke.return_value = side_effect - - with patch.object(supervisor_agent, "_planner_chain", mock_chain): - if expected_error: - with pytest.raises(type(expected_error)) as exc_info: - await supervisor_agent._invoke_planner(state) - assert str(expected_error) in str(exc_info.value) - else: - result = await supervisor_agent._invoke_planner(state) - assert result == expected_result - - # Verify the number of calls - assert mock_chain.ainvoke.call_count == expected_calls diff --git a/tests/unit/rag/reranker/test_reranker.py b/tests/unit/rag/reranker/test_reranker.py index e8206c64..e0ccbd8b 100644 --- a/tests/unit/rag/reranker/test_reranker.py +++ b/tests/unit/rag/reranker/test_reranker.py @@ -149,8 +149,9 @@ async def test_arerank( # Then assert actual_docs_list == expected_docs_list - reranker.chain.ainvoke.assert_called_once_with( - { + reranker.chain.ainvoke.assert_called_with( + config=None, + input={ "documents": format_documents(given_relevant_docs), "queries": format_queries(given_queries), "limit": given_output_limit, diff --git a/tests/unit/rag/test_generator.py b/tests/unit/rag/test_generator.py index 5c6cf0d6..eb5fa421 100644 --- a/tests/unit/rag/test_generator.py +++ b/tests/unit/rag/test_generator.py @@ -112,6 +112,6 @@ async def test_agenerate( # Verify chain was called with correct arguments docs_content = "\n\n".join(doc.page_content for doc in docs_content) - mock_chain.ainvoke.assert_called_once_with( - {"context": docs_content, "query": query} + mock_chain.ainvoke.assert_called_with( + config=None, input={"context": docs_content, "query": query} ) diff --git a/tests/unit/rag/test_query_generator.py b/tests/unit/rag/test_query_generator.py index f65a4232..56180b23 100644 --- a/tests/unit/rag/test_query_generator.py +++ b/tests/unit/rag/test_query_generator.py @@ -103,4 +103,4 @@ async def test_agenerate_queries( result = await generator.agenerate_queries(query) # Then assert result == expected_output - mock_chain.ainvoke.assert_called_once_with({"query": query}) + mock_chain.ainvoke.assert_called_with(config=None, input={"query": query}) diff --git a/tests/unit/utils/test_chain.py b/tests/unit/utils/test_chain.py new file mode 100644 index 00000000..4f906a6f --- /dev/null +++ b/tests/unit/utils/test_chain.py @@ -0,0 +1,108 @@ +from typing import Any +from unittest.mock import AsyncMock, Mock + +import pytest +from langchain.chains.base import Chain + +from utils.chain import ainvoke_chain + + +@pytest.fixture +def mock_chain(): + """Fixture for creating a mock LangChain chain.""" + chain = Mock(spec=Chain) + chain.ainvoke = AsyncMock() + return chain + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "input_data,config,mock_response,expected_chain_input,expected_output,expected_calls,expected_exception", + [ + # Success cases + pytest.param( + {"query": "test question"}, # input + None, # config + {"answer": "test response"}, # mock return/side effect + {"query": "test question"}, # expected chain input + {"answer": "test response"}, # expected output + 1, # expected calls + None, # expected exception + id="dict-input-no-config", + ), + pytest.param( + "test question", # input + None, # config + {"answer": "test response"}, # mock return/side effect + {"input": "test question"}, # expected chain input + {"answer": "test response"}, # expected output + 1, # expected calls + None, # expected exception + id="single-value-input", + ), + pytest.param( + {"query": "test"}, # input + {"temperature": 0.7}, # config + {"answer": "test response"}, # mock return/side effect + {"query": "test"}, # expected chain input + {"answer": "test response"}, # expected output + 1, # expected calls + None, # expected exception + id="with-config", + ), + # Retry cases + pytest.param( + {"query": "test"}, # input + None, # config + [ # mock return/side effect + Exception("First failure"), + Exception("Second failure"), + {"answer": "success"}, + ], + {"query": "test"}, # expected chain input + {"answer": "success"}, # expected output + 3, # expected calls + None, # expected exception + id="retry-success-after-two-failures", + ), + pytest.param( + {"query": "test"}, # input + None, # config + [Exception("Persistent failure")] * 3, # mock return/side effect + {"query": "test"}, # expected chain input + None, # expected output + 3, # expected calls + Exception, # expected exception + id="max-retries-exceeded", + ), + ], +) +async def test_ainvoke_chain( + mock_chain, + input_data: dict[str, Any] | str, + config: dict[str, Any] | None, + mock_response: Any, + expected_chain_input: dict[str, Any], + expected_output: dict[str, Any] | None, + expected_calls: int, + expected_exception: type[Exception] | None, +): + """Test chain invocations with various inputs, configs, and retry scenarios.""" + # Setup + if isinstance(mock_response, list): + mock_chain.ainvoke.side_effect = mock_response + else: + mock_chain.ainvoke.return_value = mock_response + + # Execute and Assert + if expected_exception: + with pytest.raises(expected_exception): + await ainvoke_chain(mock_chain, input_data, config=config) + else: + result = await ainvoke_chain(mock_chain, input_data, config=config) + assert result == expected_output + + assert mock_chain.ainvoke.call_count == expected_calls + + # Verify first call arguments + mock_chain.ainvoke.assert_called_with(input=expected_chain_input, config=config) From 5a682c5d70fa904ab080ddedcb0e8c359d9f1b83 Mon Sep 17 00:00:00 2001 From: Mansur Uralov Date: Wed, 5 Feb 2025 13:17:18 +0100 Subject: [PATCH 5/5] Refactor chain invocation and response handling for mypy - Update ainvoke_chain to use RunnableSequence and RunnableConfig - Modify response handling in supervisor and reranker to use model instantiation - Adjust type hints and return types for chain invocation utility --- poetry.lock | 930 ++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- src/utils/chain.py | 10 +- tests/unit/utils/test_chain.py | 3 +- 4 files changed, 925 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef1f4785..bb9cd4a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -255,6 +255,29 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["test"] +files = [ + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, +] + +[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" @@ -744,6 +767,49 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "datasets" +version = "3.2.0" +description = "HuggingFace community-driven open-source library of datasets" +optional = false +python-versions = ">=3.9.0" +groups = ["test"] +files = [ + {file = "datasets-3.2.0-py3-none-any.whl", hash = "sha256:f3d2ba2698b7284a4518019658596a6a8bc79f31e51516524249d6c59cf0fe2a"}, + {file = "datasets-3.2.0.tar.gz", hash = "sha256:9a6e1a356052866b5dbdd9c9eedb000bf3fc43d986e3584d9b028f4976937229"}, +] + +[package.dependencies] +aiohttp = "*" +dill = ">=0.3.0,<0.3.9" +filelock = "*" +fsspec = {version = ">=2023.1.0,<=2024.9.0", extras = ["http"]} +huggingface-hub = ">=0.23.0" +multiprocess = "<0.70.17" +numpy = ">=1.17" +packaging = "*" +pandas = "*" +pyarrow = ">=15.0.0" +pyyaml = ">=5.1" +requests = ">=2.32.2" +tqdm = ">=4.66.3" +xxhash = "*" + +[package.extras] +audio = ["librosa", "soundfile (>=0.12.1)", "soxr (>=0.4.0)"] +benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"] +dev = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch", "torch (>=2.0.0)", "torchdata", "transformers", "transformers (>=4.42.0)", "zstandard"] +docs = ["s3fs", "tensorflow (>=2.6.0)", "torch", "transformers"] +jax = ["jax (>=0.3.14)", "jaxlib (>=0.3.14)"] +quality = ["ruff (>=0.3.0)"] +s3 = ["s3fs"] +tensorflow = ["tensorflow (>=2.6.0)"] +tensorflow-gpu = ["tensorflow (>=2.6.0)"] +tests = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch (>=2.0.0)", "torchdata", "transformers (>=4.42.0)", "zstandard"] +tests-numpy2 = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tiktoken", "torch (>=2.0.0)", "torchdata", "transformers (>=4.42.0)", "zstandard"] +torch = ["torch"] +vision = ["Pillow (>=9.4.0)"] + [[package]] name = "dateparser" version = "1.2.0" @@ -781,17 +847,18 @@ files = [ [[package]] name = "deepeval" -version = "2.2.6" +version = "2.3.2" description = "The Open-Source LLM Evaluation Framework." optional = false python-versions = "<3.14,>=3.9" groups = ["test"] files = [ - {file = "deepeval-2.2.6-py3-none-any.whl", hash = "sha256:0148a3fc05bff9aa4a21b7c4bd23c7b610957469fe55440f5826b4b7c38797a3"}, - {file = "deepeval-2.2.6.tar.gz", hash = "sha256:337b5a8a07dc94e5f72c6db0f5670bc77445edb662a019ccec61f438d150c281"}, + {file = "deepeval-2.3.2-py3-none-any.whl", hash = "sha256:e32e39c5c1c7606a4850169bd4c0ff72809eabcc30eed91b4d6ee451491a3f3c"}, + {file = "deepeval-2.3.2.tar.gz", hash = "sha256:8f52768908e5cfbfb3e9ca772d0605f98c2500ae874f9657bc43df36227a4a41"}, ] [package.dependencies] +datasets = "*" docx2txt = ">=0.8,<1.0" grpcio = "1.67.1" importlib-metadata = ">=6.0.2" @@ -799,6 +866,7 @@ langchain = "*" langchain-community = "*" langchain-core = "*" langchain_openai = "*" +llama-index = "*" nest-asyncio = "*" opentelemetry-api = ">=1.24.0,<2.0.0" opentelemetry-exporter-otlp-proto-grpc = ">=1.24.0,<2.0.0" @@ -838,6 +906,34 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "dirtyjson" +version = "1.0.8" +description = "JSON decoder for Python that can extract data from the muck" +optional = false +python-versions = "*" +groups = ["test"] +files = [ + {file = "dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53"}, + {file = "dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd"}, +] + [[package]] name = "distlib" version = "0.3.9" @@ -1120,7 +1216,7 @@ version = "3.17.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, @@ -1131,6 +1227,18 @@ 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 = "filetype" +version = "1.2.0" +description = "Infer file type and MIME type of any file/buffer. No external dependencies." +optional = false +python-versions = "*" +groups = ["test"] +files = [ + {file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"}, + {file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"}, +] + [[package]] name = "flake8" version = "7.1.1" @@ -1269,6 +1377,49 @@ files = [ {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, ] +[[package]] +name = "fsspec" +version = "2024.9.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b"}, + {file = "fsspec-2024.9.0.tar.gz", hash = "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8"}, +] + +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + [[package]] name = "generative-ai-hub-sdk" version = "4.1.1" @@ -1911,6 +2062,41 @@ files = [ {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, ] +[[package]] +name = "huggingface-hub" +version = "0.28.1" +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 = ["test"] +files = [ + {file = "huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7"}, + {file = "huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["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)"] +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)"] +inference = ["aiohttp"] +quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +tensorflow-testing = ["keras (<3.0)", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["safetensors[torch]", "torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + [[package]] name = "idna" version = "3.10" @@ -2228,7 +2414,7 @@ version = "1.4.2" description = "Lightweight pipelining with Python functions" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, @@ -2568,6 +2754,272 @@ zstandard = ">=0.23.0,<0.24.0" langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"] +[[package]] +name = "llama-cloud" +version = "0.1.11" +description = "" +optional = false +python-versions = "<4,>=3.8" +groups = ["test"] +files = [ + {file = "llama_cloud-0.1.11-py3-none-any.whl", hash = "sha256:b703765d03783a5a0fc57a52adc9892f8b91b0c19bbecb85a54ad4e813342951"}, + {file = "llama_cloud-0.1.11.tar.gz", hash = "sha256:d4be5b48659fd9fe1698727be257269a22d7f2733a2ed11bce7065768eb94cbe"}, +] + +[package.dependencies] +certifi = ">=2024.7.4,<2025.0.0" +httpx = ">=0.20.0" +pydantic = ">=1.10" + +[[package]] +name = "llama-index" +version = "0.12.16" +description = "Interface between LLMs and your data" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index-0.12.16-py3-none-any.whl", hash = "sha256:c94d0cf6735219d97d91e2eca5bcfac89ec1583990917f934b075d5a45686cf6"}, + {file = "llama_index-0.12.16.tar.gz", hash = "sha256:4fd5f5b94eb3f8dd470bb8cc0e1b985d931e8f31473266ef69855488fd8ae3f2"}, +] + +[package.dependencies] +llama-index-agent-openai = ">=0.4.0,<0.5.0" +llama-index-cli = ">=0.4.0,<0.5.0" +llama-index-core = ">=0.12.16,<0.13.0" +llama-index-embeddings-openai = ">=0.3.0,<0.4.0" +llama-index-indices-managed-llama-cloud = ">=0.4.0" +llama-index-llms-openai = ">=0.3.0,<0.4.0" +llama-index-multi-modal-llms-openai = ">=0.4.0,<0.5.0" +llama-index-program-openai = ">=0.3.0,<0.4.0" +llama-index-question-gen-openai = ">=0.3.0,<0.4.0" +llama-index-readers-file = ">=0.4.0,<0.5.0" +llama-index-readers-llama-parse = ">=0.4.0" +nltk = ">3.8.1" + +[[package]] +name = "llama-index-agent-openai" +version = "0.4.3" +description = "llama-index agent openai integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_agent_openai-0.4.3-py3-none-any.whl", hash = "sha256:5d1fbb6831113e609296e457b0a4d1c08c9267acca219eb78cb702bd76a0744d"}, + {file = "llama_index_agent_openai-0.4.3.tar.gz", hash = "sha256:ff1f4a13ba417cb4b9cfbc2ffa9f162bdbdda9b87d6645d512cbde2061f55412"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.11,<0.13.0" +llama-index-llms-openai = ">=0.3.0,<0.4.0" +openai = ">=1.14.0" + +[[package]] +name = "llama-index-cli" +version = "0.4.0" +description = "llama-index cli" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_cli-0.4.0-py3-none-any.whl", hash = "sha256:60d12f89e6b85e80a0cc3a8b531f05a911b5eebaebc37314411476d1ba685904"}, + {file = "llama_index_cli-0.4.0.tar.gz", hash = "sha256:d6ab201359962a8a34368aeda3a49bbbe67e9e009c59bd925c4fb2be4ace3906"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.0,<0.13.0" +llama-index-embeddings-openai = ">=0.3.0,<0.4.0" +llama-index-llms-openai = ">=0.3.0,<0.4.0" + +[[package]] +name = "llama-index-core" +version = "0.12.16.post1" +description = "Interface between LLMs and your data" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_core-0.12.16.post1-py3-none-any.whl", hash = "sha256:95904a44f25e122a45963541c56a50c4daf2ffaf062d1a3224c84a6dc9e6801f"}, + {file = "llama_index_core-0.12.16.post1.tar.gz", hash = "sha256:8fed0554ae71b6c1f80b53164723af28c887951eef7aa1b44ba6c8103c0efb2c"}, +] + +[package.dependencies] +aiohttp = ">=3.8.6,<4.0.0" +dataclasses-json = "*" +deprecated = ">=1.2.9.3" +dirtyjson = ">=1.0.8,<2.0.0" +filetype = ">=1.2.0,<2.0.0" +fsspec = ">=2023.5.0" +httpx = "*" +nest-asyncio = ">=1.5.8,<2.0.0" +networkx = ">=3.0" +nltk = ">3.8.1" +numpy = "*" +pillow = ">=9.0.0" +pydantic = ">=2.8.0" +PyYAML = ">=6.0.1" +requests = ">=2.31.0" +SQLAlchemy = {version = ">=1.4.49", extras = ["asyncio"]} +tenacity = ">=8.2.0,<8.4.0 || >8.4.0,<10.0.0" +tiktoken = ">=0.3.3" +tqdm = ">=4.66.1,<5.0.0" +typing-extensions = ">=4.5.0" +typing-inspect = ">=0.8.0" +wrapt = "*" + +[[package]] +name = "llama-index-embeddings-openai" +version = "0.3.1" +description = "llama-index embeddings openai integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_embeddings_openai-0.3.1-py3-none-any.whl", hash = "sha256:f15a3d13da9b6b21b8bd51d337197879a453d1605e625a1c6d45e741756c0290"}, + {file = "llama_index_embeddings_openai-0.3.1.tar.gz", hash = "sha256:1368aad3ce24cbaed23d5ad251343cef1eb7b4a06d6563d6606d59cb347fef20"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.0,<0.13.0" +openai = ">=1.1.0" + +[[package]] +name = "llama-index-indices-managed-llama-cloud" +version = "0.6.4" +description = "llama-index indices llama-cloud integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_indices_managed_llama_cloud-0.6.4-py3-none-any.whl", hash = "sha256:d7e85844a2e343dacebdef424decab3f5fd6361e25b3ff2bdcfb18607c1a49c5"}, + {file = "llama_index_indices_managed_llama_cloud-0.6.4.tar.gz", hash = "sha256:0b45973cb2dc9702122006019bfb556dcabba31b0bdf79afc7b376ca8143df03"}, +] + +[package.dependencies] +llama-cloud = ">=0.1.8,<0.2.0" +llama-index-core = ">=0.12.0,<0.13.0" + +[[package]] +name = "llama-index-llms-openai" +version = "0.3.17" +description = "llama-index llms openai integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_llms_openai-0.3.17-py3-none-any.whl", hash = "sha256:06ae687bfbe17f9eadc09c7167ac2a67d8ff5548164166746be251698f5e7b58"}, + {file = "llama_index_llms_openai-0.3.17.tar.gz", hash = "sha256:19f478054a017e5e3c16f4335a7e149a1f28cc59871c9873adc49a46b594ac99"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.4,<0.13.0" +openai = ">=1.58.1,<2.0.0" + +[[package]] +name = "llama-index-multi-modal-llms-openai" +version = "0.4.3" +description = "llama-index multi-modal-llms openai integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_multi_modal_llms_openai-0.4.3-py3-none-any.whl", hash = "sha256:1ceb42716472ac8bd5130afa29b793869d367946aedd02e48a3b03184e443ad1"}, + {file = "llama_index_multi_modal_llms_openai-0.4.3.tar.gz", hash = "sha256:5e6ca54069d3d18c2f5f7ca34f3720fba1d1b9126482ad38feb0c858f4feb63b"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.3,<0.13.0" +llama-index-llms-openai = ">=0.3.0,<0.4.0" + +[[package]] +name = "llama-index-program-openai" +version = "0.3.1" +description = "llama-index program openai integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_program_openai-0.3.1-py3-none-any.whl", hash = "sha256:93646937395dc5318fd095153d2f91bd632b25215d013d14a87c088887d205f9"}, + {file = "llama_index_program_openai-0.3.1.tar.gz", hash = "sha256:6039a6cdbff62c6388c07e82a157fe2edd3bbef0c5adf292ad8546bf4ec75b82"}, +] + +[package.dependencies] +llama-index-agent-openai = ">=0.4.0,<0.5.0" +llama-index-core = ">=0.12.0,<0.13.0" +llama-index-llms-openai = ">=0.3.0,<0.4.0" + +[[package]] +name = "llama-index-question-gen-openai" +version = "0.3.0" +description = "llama-index question_gen openai integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_question_gen_openai-0.3.0-py3-none-any.whl", hash = "sha256:9b60ec114273a63b50349948666e5744a8f58acb645824e07c979041e8fec598"}, + {file = "llama_index_question_gen_openai-0.3.0.tar.gz", hash = "sha256:efd3b468232808e9d3474670aaeab00e41b90f75f52d0c9bfbf11207e0963d62"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.0,<0.13.0" +llama-index-llms-openai = ">=0.3.0,<0.4.0" +llama-index-program-openai = ">=0.3.0,<0.4.0" + +[[package]] +name = "llama-index-readers-file" +version = "0.4.4" +description = "llama-index readers file integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_readers_file-0.4.4-py3-none-any.whl", hash = "sha256:01589a4895e2d4abad30294c9b0d2813520ee1f5164922ad92f11e64a1d65d6c"}, + {file = "llama_index_readers_file-0.4.4.tar.gz", hash = "sha256:e076b3fa1e68eea1594d47cec1f64b384fb6067f2697ca8aae22b4a21ad27ca7"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.12.3,<5.0.0" +llama-index-core = ">=0.12.0,<0.13.0" +pandas = "*" +pypdf = ">=5.1.0,<6.0.0" +striprtf = ">=0.0.26,<0.0.27" + +[package.extras] +pymupdf = ["pymupdf (>=1.23.21,<2.0.0)"] + +[[package]] +name = "llama-index-readers-llama-parse" +version = "0.4.0" +description = "llama-index readers llama-parse integration" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_index_readers_llama_parse-0.4.0-py3-none-any.whl", hash = "sha256:574e48386f28d2c86c3f961ca4a4906910312f3400dd0c53014465bfbc6b32bf"}, + {file = "llama_index_readers_llama_parse-0.4.0.tar.gz", hash = "sha256:e99ec56f4f8546d7fda1a7c1ae26162fb9acb7ebcac343b5abdb4234b4644e0f"}, +] + +[package.dependencies] +llama-index-core = ">=0.12.0,<0.13.0" +llama-parse = ">=0.5.0" + +[[package]] +name = "llama-parse" +version = "0.5.20" +description = "Parse files into RAG-Optimized formats." +optional = false +python-versions = "<4.0,>=3.9" +groups = ["test"] +files = [ + {file = "llama_parse-0.5.20-py3-none-any.whl", hash = "sha256:9617edb3428d3218ea01f1708f0b6105f3ffef142fedbeb8c98d50082c37e226"}, + {file = "llama_parse-0.5.20.tar.gz", hash = "sha256:649e256431d3753025b9a320bb03b76849ce4b5a1121394c803df543e6c1006f"}, +] + +[package.dependencies] +click = ">=8.1.7,<9.0.0" +llama-index-core = ">=0.11.0" +pydantic = "!=2.10" + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -2911,6 +3363,31 @@ files = [ {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] +[[package]] +name = "multiprocess" +version = "0.70.16" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee"}, + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37b55f71c07e2d741374998c043b9520b626a8dddc8b3129222ca4f1a06ef67a"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba8c31889abf4511c7308a8c52bb4a30b9d590e7f58523302ba00237702ca054"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:0dfd078c306e08d46d7a8d06fb120313d87aa43af60d66da43ffff40b44d2f41"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e7b9d0f307cd9bd50851afaac0dba2cb6c44449efff697df7c7645f7d3f2be3a"}, + {file = "multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02"}, + {file = "multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a"}, + {file = "multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e"}, + {file = "multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435"}, + {file = "multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3"}, + {file = "multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1"}, +] + +[package.dependencies] +dill = ">=0.3.8" + [[package]] name = "mypy" version = "1.14.1" @@ -2994,13 +3471,33 @@ files = [ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] +[[package]] +name = "networkx" +version = "3.4.2" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, + {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, +] + +[package.extras] +default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + [[package]] name = "nltk" version = "3.9.1" description = "Natural Language Toolkit" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, @@ -3323,6 +3820,89 @@ files = [ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version >= \"3.12\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "parso" version = "0.8.4" @@ -3391,6 +3971,95 @@ files = [ {file = "phonenumbers-8.13.53.tar.gz", hash = "sha256:b7308f21837defa567b4f961925b6c652dd5148f3e0fadbd158a10758cc63d91"}, ] +[[package]] +name = "pillow" +version = "11.1.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"}, + {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"}, + {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"}, + {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"}, + {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"}, + {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"}, + {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"}, + {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"}, + {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"}, + {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"}, + {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"}, + {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"}, + {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"}, + {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"}, + {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"}, + {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"}, + {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"}, + {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"}, + {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"}, + {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"}, + {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"}, + {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"}, + {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"}, + {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"}, + {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"}, + {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"}, + {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"}, + {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"}, + {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"}, + {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"}, + {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"}, + {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"}, + {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"}, + {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"}, + {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"}, + {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"}, + {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"}, + {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"}, + {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "pkginfo" version = "1.12.0" @@ -3730,6 +4399,61 @@ files = [ [package.extras] tests = ["pytest"] +[[package]] +name = "pyarrow" +version = "19.0.0" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pyarrow-19.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:c318eda14f6627966997a7d8c374a87d084a94e4e38e9abbe97395c215830e0c"}, + {file = "pyarrow-19.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:62ef8360ff256e960f57ce0299090fb86423afed5e46f18f1225f960e05aae3d"}, + {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2795064647add0f16563e57e3d294dbfc067b723f0fd82ecd80af56dad15f503"}, + {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a218670b26fb1bc74796458d97bcab072765f9b524f95b2fccad70158feb8b17"}, + {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:66732e39eaa2247996a6b04c8aa33e3503d351831424cdf8d2e9a0582ac54b34"}, + {file = "pyarrow-19.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e675a3ad4732b92d72e4d24009707e923cab76b0d088e5054914f11a797ebe44"}, + {file = "pyarrow-19.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f094742275586cdd6b1a03655ccff3b24b2610c3af76f810356c4c71d24a2a6c"}, + {file = "pyarrow-19.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8e3a839bf36ec03b4315dc924d36dcde5444a50066f1c10f8290293c0427b46a"}, + {file = "pyarrow-19.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ce42275097512d9e4e4a39aade58ef2b3798a93aa3026566b7892177c266f735"}, + {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9348a0137568c45601b031a8d118275069435f151cbb77e6a08a27e8125f59d4"}, + {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0144a712d990d60f7f42b7a31f0acaccf4c1e43e957f7b1ad58150d6f639c1"}, + {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2a1a109dfda558eb011e5f6385837daffd920d54ca00669f7a11132d0b1e6042"}, + {file = "pyarrow-19.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:be686bf625aa7b9bada18defb3a3ea3981c1099697239788ff111d87f04cd263"}, + {file = "pyarrow-19.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:239ca66d9a05844bdf5af128861af525e14df3c9591bcc05bac25918e650d3a2"}, + {file = "pyarrow-19.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:a7bbe7109ab6198688b7079cbad5a8c22de4d47c4880d8e4847520a83b0d1b68"}, + {file = "pyarrow-19.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:4624c89d6f777c580e8732c27bb8e77fd1433b89707f17c04af7635dd9638351"}, + {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b6d3ce4288793350dc2d08d1e184fd70631ea22a4ff9ea5c4ff182130249d9b"}, + {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450a7d27e840e4d9a384b5c77199d489b401529e75a3b7a3799d4cd7957f2f9c"}, + {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a08e2a8a039a3f72afb67a6668180f09fddaa38fe0d21f13212b4aba4b5d2451"}, + {file = "pyarrow-19.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f43f5aef2a13d4d56adadae5720d1fed4c1356c993eda8b59dace4b5983843c1"}, + {file = "pyarrow-19.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f672f5364b2d7829ef7c94be199bb88bf5661dd485e21d2d37de12ccb78a136"}, + {file = "pyarrow-19.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463"}, + {file = "pyarrow-19.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352"}, + {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d"}, + {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae"}, + {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098"}, + {file = "pyarrow-19.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04"}, + {file = "pyarrow-19.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9"}, + {file = "pyarrow-19.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560"}, + {file = "pyarrow-19.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849"}, + {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e"}, + {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5"}, + {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73"}, + {file = "pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89"}, + {file = "pyarrow-19.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:c0423393e4a07ff6fea08feb44153302dd261d0551cc3b538ea7a5dc853af43a"}, + {file = "pyarrow-19.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:718947fb6d82409013a74b176bf93e0f49ef952d8a2ecd068fecd192a97885b7"}, + {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1c162c4660e0978411a4761f91113dde8da3433683efa473501254563dcbe8"}, + {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73268cf557e688efb60f1ccbc7376f7e18cd8e2acae9e663e98b194c40c1a2d"}, + {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:edfe6d3916e915ada9acc4e48f6dafca7efdbad2e6283db6fd9385a1b23055f1"}, + {file = "pyarrow-19.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:da410b70a7ab8eb524112f037a7a35da7128b33d484f7671a264a4c224ac131d"}, + {file = "pyarrow-19.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:597360ffc71fc8cceea1aec1fb60cb510571a744fffc87db33d551d5de919bec"}, + {file = "pyarrow-19.0.0.tar.gz", hash = "sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b"}, +] + +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] + [[package]] name = "pyasn1" version = "0.6.1" @@ -3965,6 +4689,26 @@ files = [ {file = "pyhumps-3.8.0.tar.gz", hash = "sha256:498026258f7ee1a8e447c2e28526c0bea9407f9a59c03260aee4bd6c04d681a3"}, ] +[[package]] +name = "pypdf" +version = "5.2.0" +description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "pypdf-5.2.0-py3-none-any.whl", hash = "sha256:d107962ec45e65e3bd10c1d9242bdbbedaa38193c9e3a6617bd6d996e5747b19"}, + {file = "pypdf-5.2.0.tar.gz", hash = "sha256:7c38e68420f038f2c4998fd9d6717b6db4f6cef1642e9cf384d519c9cf094663"}, +] + +[package.extras] +crypto = ["cryptography"] +cryptodome = ["PyCryptodome"] +dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"] +docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] +full = ["Pillow (>=8.0.0)", "cryptography"] +image = ["Pillow (>=8.0.0)"] + [[package]] name = "pyproject-hooks" version = "1.2.0" @@ -4109,7 +4853,7 @@ 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 = ["main"] +groups = ["main", "test"] 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"}, @@ -4180,7 +4924,7 @@ version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, @@ -4949,7 +5693,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "test"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -4979,6 +5723,18 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + [[package]] name = "sqlalchemy" version = "2.0.37" @@ -5047,7 +5803,10 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +greenlet = [ + {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}, + {version = "!=0.4.17", optional = true, markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") or extra == \"asyncio\""}, +] typing-extensions = ">=4.6.0" [package.extras] @@ -5113,6 +5872,18 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +[[package]] +name = "striprtf" +version = "0.0.26" +description = "A simple library to convert rtf to text" +optional = false +python-versions = "*" +groups = ["test"] +files = [ + {file = "striprtf-0.0.26-py3-none-any.whl", hash = "sha256:8c8f9d32083cdc2e8bfb149455aa1cc5a4e0a035893bedc75db8b73becb3a1bb"}, + {file = "striprtf-0.0.26.tar.gz", hash = "sha256:fdb2bba7ac440072d1c41eab50d8d74ae88f60a8b6575c6e2c7805dc462093aa"}, +] + [[package]] name = "tabulate" version = "0.9.0" @@ -5422,12 +6193,12 @@ version = "2025.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" -groups = ["main"] -markers = "platform_system == \"Windows\"" +groups = ["main", "test"] files = [ {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, ] +markers = {main = "platform_system == \"Windows\""} [[package]] name = "tzlocal" @@ -5933,6 +6704,139 @@ cffi = ">=1.16.0" [package.extras] test = ["pytest"] +[[package]] +name = "xxhash" +version = "3.5.0" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, + {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442"}, + {file = "xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da"}, + {file = "xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9"}, + {file = "xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839"}, + {file = "xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da"}, + {file = "xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58"}, + {file = "xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"}, + {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"}, + {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"}, + {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"}, + {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"}, + {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"}, + {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"}, + {file = "xxhash-3.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e5f70f6dca1d3b09bccb7daf4e087075ff776e3da9ac870f86ca316736bb4aa"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e76e83efc7b443052dd1e585a76201e40b3411fe3da7af4fe434ec51b2f163b"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33eac61d0796ca0591f94548dcfe37bb193671e0c9bcf065789b5792f2eda644"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ec70a89be933ea49222fafc3999987d7899fc676f688dd12252509434636622"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86b8e7f703ec6ff4f351cfdb9f428955859537125904aa8c963604f2e9d3e7"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0adfbd36003d9f86c8c97110039f7539b379f28656a04097e7434d3eaf9aa131"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:63107013578c8a730419adc05608756c3fa640bdc6abe806c3123a49fb829f43"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:683b94dbd1ca67557850b86423318a2e323511648f9f3f7b1840408a02b9a48c"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5d2a01dcce81789cf4b12d478b5464632204f4c834dc2d064902ee27d2d1f0ee"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a9d360a792cbcce2fe7b66b8d51274ec297c53cbc423401480e53b26161a290d"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f0b48edbebea1b7421a9c687c304f7b44d0677c46498a046079d445454504737"}, + {file = "xxhash-3.5.0-cp37-cp37m-win32.whl", hash = "sha256:7ccb800c9418e438b44b060a32adeb8393764da7441eb52aa2aa195448935306"}, + {file = "xxhash-3.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c3bc7bf8cb8806f8d1c9bf149c18708cb1c406520097d6b0a73977460ea03602"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74752ecaa544657d88b1d1c94ae68031e364a4d47005a90288f3bab3da3c970f"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dee1316133c9b463aa81aca676bc506d3f80d8f65aeb0bba2b78d0b30c51d7bd"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:602d339548d35a8579c6b013339fb34aee2df9b4e105f985443d2860e4d7ffaa"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:695735deeddfb35da1677dbc16a083445360e37ff46d8ac5c6fcd64917ff9ade"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1030a39ba01b0c519b1a82f80e8802630d16ab95dc3f2b2386a0b5c8ed5cbb10"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bc08f33c4966f4eb6590d6ff3ceae76151ad744576b5fc6c4ba8edd459fdec"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e0c19ee500482ddfb5d5570a0415f565d8ae2b3fd69c5dcfce8a58107b1c3"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f1abffa122452481a61c3551ab3c89d72238e279e517705b8b03847b1d93d738"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d5e9db7ef3ecbfc0b4733579cea45713a76852b002cf605420b12ef3ef1ec148"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:23241ff6423378a731d84864bf923a41649dc67b144debd1077f02e6249a0d54"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:82b833d5563fefd6fceafb1aed2f3f3ebe19f84760fdd289f8b926731c2e6e91"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a80ad0ffd78bef9509eee27b4a29e56f5414b87fb01a888353e3d5bda7038bd"}, + {file = "xxhash-3.5.0-cp38-cp38-win32.whl", hash = "sha256:50ac2184ffb1b999e11e27c7e3e70cc1139047e7ebc1aa95ed12f4269abe98d4"}, + {file = "xxhash-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:392f52ebbb932db566973693de48f15ce787cabd15cf6334e855ed22ea0be5b3"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606"}, + {file = "xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4"}, + {file = "xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558"}, + {file = "xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b4154c00eb22e4d543f472cfca430e7962a0f1d0f3778334f2e08a7ba59363c"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d30bbc1644f726b825b3278764240f449d75f1a8bdda892e641d4a688b1494ae"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa0b72f2423e2aa53077e54a61c28e181d23effeaafd73fcb9c494e60930c8e"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13de2b76c1835399b2e419a296d5b38dc4855385d9e96916299170085ef72f57"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0691bfcc4f9c656bcb96cc5db94b4d75980b9d5589f2e59de790091028580837"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:297595fe6138d4da2c8ce9e72a04d73e58725bb60f3a19048bc96ab2ff31c692"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1276d369452040cbb943300dc8abeedab14245ea44056a2943183822513a18"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2061188a1ba352fc699c82bff722f4baacb4b4b8b2f0c745d2001e56d0dfb514"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c384c434021e4f62b8d9ba0bc9467e14d394893077e2c66d826243025e1f81"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e6a4dd644d72ab316b580a1c120b375890e4c52ec392d4aef3c63361ec4d77d1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240"}, + {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, +] + [[package]] name = "yarl" version = "1.18.3" @@ -6166,4 +7070,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "~3.12" -content-hash = "de5faafa8fb2b356355ee4e406f31ebefdc03891f4e706a9949bfd610f1bf879" +content-hash = "ef43907d6af45db81ff8bc282b2f58df5139d9c0cd19e23ee451584c43ecb170" diff --git a/pyproject.toml b/pyproject.toml index d0026714..d6bb1679 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ tenacity = "^9.0.0" tiktoken = "^0.7.0" [tool.poetry.group.test.dependencies] -deepeval = "^2.1.2" +deepeval = "^2.3.2" fakeredis = "^2.23.3" prettytable = "^3.10.2" pytest = "^8.2.2" diff --git a/src/utils/chain.py b/src/utils/chain.py index 88685740..5a9f3214 100644 --- a/src/utils/chain.py +++ b/src/utils/chain.py @@ -1,7 +1,7 @@ import logging from typing import Any -from langchain.chains.base import Chain +from langchain.schema.runnable import RunnableConfig, RunnableSequence from tenacity import ( RetryCallState, retry, @@ -34,11 +34,11 @@ def after_log(retry_state: RetryCallState) -> None: reraise=True, ) async def ainvoke_chain( - chain: Chain, + chain: RunnableSequence, inputs: dict[str, Any] | Any, *, - config: dict[str, Any] | None = None, -) -> dict[str, Any]: + config: RunnableConfig | None = None, +) -> Any: """Invokes a LangChain chain asynchronously. Retries the LLM calls if they fail with the provided wait strategy. Tries 3 times, waits 2 seconds between attempts, i.e. 2, 5. @@ -52,7 +52,7 @@ async def ainvoke_chain( Defaults to None. Returns: - Dict[str, Any]: The chain execution results + Any: The chain execution results """ # Convert single value input to dict if needed chain_inputs = inputs if isinstance(inputs, dict) else {"input": inputs} diff --git a/tests/unit/utils/test_chain.py b/tests/unit/utils/test_chain.py index 4f906a6f..6d2a667d 100644 --- a/tests/unit/utils/test_chain.py +++ b/tests/unit/utils/test_chain.py @@ -3,6 +3,7 @@ import pytest from langchain.chains.base import Chain +from langchain.schema.runnable import RunnableConfig from utils.chain import ainvoke_chain @@ -80,7 +81,7 @@ def mock_chain(): async def test_ainvoke_chain( mock_chain, input_data: dict[str, Any] | str, - config: dict[str, Any] | None, + config: RunnableConfig | None, mock_response: Any, expected_chain_input: dict[str, Any], expected_output: dict[str, Any] | None,