From b73563699b23ab35fe26f0b0c1f4c7bdbb193c7a Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Wed, 9 Jul 2025 14:06:43 +0000 Subject: [PATCH 01/12] Add langchain-oracledb package --- .gitignore | 1 + README.md | 147 +- libs/oracledb/LICENSE | 21 + libs/oracledb/Makefile | 59 + libs/oracledb/README.md | 278 ++ libs/oracledb/langchain_oracledb/__init__.py | 22 + .../document_loaders/__init__.py | 12 + .../document_loaders/oracleadb_loader.py | 134 + .../document_loaders/oracleai.py | 422 ++ .../langchain_oracledb/embeddings/__init__.py | 6 + .../langchain_oracledb/embeddings/oracleai.py | 191 + .../langchain_oracledb/utilities/__init__.py | 6 + .../langchain_oracledb/utilities/oracleai.py | 198 + .../vectorstores/__init__.py | 6 + .../vectorstores/oraclevs.py | 1049 +++++ libs/oracledb/poetry.lock | 3898 +++++++++++++++++ libs/oracledb/pyproject.toml | 99 + libs/oracledb/scripts/check_imports.py | 20 + libs/oracledb/scripts/lint_imports.sh | 18 + libs/oracledb/tests/__init__.py | 2 + .../tests/integration_tests/__init__.py | 2 + .../document_loaders/__init__.py | 2 + .../document_loaders/test_oracleds.py | 445 ++ .../vectorstores/__init__.py | 2 + .../vectorstores/test_oraclevs.py | 956 ++++ libs/oracledb/tests/unit_tests/__init__.py | 11 + .../unit_tests/document_loaders/__init__.py | 2 + .../document_loaders/test_oracleadb.py | 56 + .../oracledb/tests/unit_tests/test_imports.py | 18 + 29 files changed, 7956 insertions(+), 127 deletions(-) create mode 100644 libs/oracledb/LICENSE create mode 100644 libs/oracledb/Makefile create mode 100644 libs/oracledb/README.md create mode 100644 libs/oracledb/langchain_oracledb/__init__.py create mode 100644 libs/oracledb/langchain_oracledb/document_loaders/__init__.py create mode 100644 libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py create mode 100644 libs/oracledb/langchain_oracledb/document_loaders/oracleai.py create mode 100644 libs/oracledb/langchain_oracledb/embeddings/__init__.py create mode 100644 libs/oracledb/langchain_oracledb/embeddings/oracleai.py create mode 100644 libs/oracledb/langchain_oracledb/utilities/__init__.py create mode 100644 libs/oracledb/langchain_oracledb/utilities/oracleai.py create mode 100644 libs/oracledb/langchain_oracledb/vectorstores/__init__.py create mode 100644 libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py create mode 100644 libs/oracledb/poetry.lock create mode 100644 libs/oracledb/pyproject.toml create mode 100644 libs/oracledb/scripts/check_imports.py create mode 100755 libs/oracledb/scripts/lint_imports.sh create mode 100644 libs/oracledb/tests/__init__.py create mode 100644 libs/oracledb/tests/integration_tests/__init__.py create mode 100644 libs/oracledb/tests/integration_tests/document_loaders/__init__.py create mode 100644 libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py create mode 100644 libs/oracledb/tests/integration_tests/vectorstores/__init__.py create mode 100644 libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py create mode 100644 libs/oracledb/tests/unit_tests/__init__.py create mode 100644 libs/oracledb/tests/unit_tests/document_loaders/__init__.py create mode 100644 libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py create mode 100644 libs/oracledb/tests/unit_tests/test_imports.py diff --git a/.gitignore b/.gitignore index 2b7ead3..2a7cc71 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ __pycache__ .env .venv* .idea +.coverage diff --git a/README.md b/README.md index d0bf491..36f95ca 100644 --- a/README.md +++ b/README.md @@ -1,154 +1,47 @@ # ๐Ÿฆœ๏ธ๐Ÿ”— LangChain ๐Ÿค Oracle -Welcome to the official repository for LangChain integration with [Oracle Cloud Infrastructure (OCI)](https://cloud.oracle.com/). This library provides native LangChain components for interacting with Oracle's AI servicesโ€”combining support for **OCI Generative AI** and **OCI Data Science**. +Welcome to the official repository for LangChain integration with [Oracle Cloud Infrastructure (OCI)](https://cloud.oracle.com/) and [Oracle AI Vector Search](https://www.oracle.com/database/ai-vector-search/). This project provides native LangChain components for interacting with Oracle's AI servicesโ€”providing support for **OCI Generative AI**, **OCI Data Science** and **Oracle AI Vector Search**. ## Features - **LLMs**: Includes LLM classes for OCI services like [Generative AI](https://cloud.oracle.com/ai-services/generative-ai) and [ModelDeployment Endpoints](https://cloud.oracle.com/ai-services/model-deployment), allowing you to leverage their language models within LangChain. - **Agents**: Includes Runnables to support [Oracle Generative AI Agents](https://www.oracle.com/artificial-intelligence/generative-ai/agents/), allowing you to leverage Generative AI Agents within LangChain and LangGraph. -- **More to come**: This repository will continue to expand and offer additional components for various OCI services as development progresses. +- **Vector Search**: Offers native integration with [Oracle AI Vector Search](https://www.oracle.com/database/ai-vector-search/) through a LangChain-compatible components. This enables pipelines that can: + - Load the documents from various sources using `OracleDocLoader` + - Summarize content within/outside the database using `OracleSummary` + - Generate embeddings within/outside the database using `OracleEmbeddings` + - Chunk according to different requirements using Advanced Oracle Capabilities from `OracleTextSplitter` + - Store, index, and query vectors using `OracleVS` +- **More to come**: This repository will continue to expand and offer additional components for various OCI and Oracle AI services as development progresses. -> This project merges and replaces earlier OCI integrations from the `langchain-community` repository and unifies contributions from Oracle's GenAI and Data Science teams. -> All integrations in this package assume that you have the credentials setup to connect with oci services. +> This project merges and replaces earlier OCI and Oracle AI Vector Search integrations from the `langchain-community` repository and unifies contributions from Oracle teams. +> All integrations in this package assume that you have the credentials setup to connect with oci and database services. --- ## Installation +For OCI services: ```bash pip install -U langchain-oci ``` ---- - -## Quick Start - -This repository includes two main integration categories: - -- [OCI Generative AI](#oci-generative-ai-examples) -- [OCI Data Science (Model Deployment)](#oci-data-science-model-deployment-examples) - - ---- - -## OCI Generative AI Examples - -### 1. Use a Chat Model - -`ChatOCIGenAI` class exposes chat models from OCI Generative AI. - -```python -from langchain_oci import ChatOCIGenAI - -llm = ChatOCIGenAI() -llm.invoke("Sing a ballad of LangChain.") -``` - -### 2. Use a Completion Model -`OCIGenAI` class exposes LLMs from OCI Generative AI. - -```python -from langchain_oci import OCIGenAI - -llm = OCIGenAI() -llm.invoke("The meaning of life is") -``` +For Oracle AI Vector Search services: -### 3. Use an Embedding Model -`OCIGenAIEmbeddings` class exposes embeddings from OCI Generative AI. - -```python -from langchain_oci import OCIGenAIEmbeddings - -embeddings = OCIGenAIEmbeddings() -embeddings.embed_query("What is the meaning of life?") -``` - - -## OCI Data Science Model Deployment Examples - -### 1. Use a Chat Model - -You may instantiate the OCI Data Science model with the generic `ChatOCIModelDeployment` or framework specific class like `ChatOCIModelDeploymentVLLM`. - -```python -from langchain_oci.chat_models import ChatOCIModelDeployment, ChatOCIModelDeploymentVLLM - -# Create an instance of OCI Model Deployment Endpoint -# Replace the endpoint uri with your own -endpoint = "https://modeldeployment..oci.customer-oci.com//predict" - -messages = [ - ( - "system", - "You are a helpful assistant that translates English to French. Translate the user sentence.", - ), - ("human", "I love programming."), -] - -chat = ChatOCIModelDeployment( - endpoint=endpoint, - streaming=True, - max_retries=1, - model_kwargs={ - "temperature": 0.2, - "max_tokens": 512, - }, # other model params... - default_headers={ - "route": "/v1/chat/completions", - # other request headers ... - }, -) -chat.invoke(messages) - -chat_vllm = ChatOCIModelDeploymentVLLM(endpoint=endpoint) -chat_vllm.invoke(messages) -``` - -### 2. Use a Completion Model -You may instantiate the OCI Data Science model with `OCIModelDeploymentLLM` or `OCIModelDeploymentVLLM`. - -```python -from langchain_oci.llms import OCIModelDeploymentLLM, OCIModelDeploymentVLLM - -# Create an instance of OCI Model Deployment Endpoint -# Replace the endpoint uri and model name with your own -endpoint = "https://modeldeployment..oci.customer-oci.com//predict" - -llm = OCIModelDeploymentLLM( - endpoint=endpoint, - model="odsc-llm", -) -llm.invoke("Who is the first president of United States?") - -vllm = OCIModelDeploymentVLLM( - endpoint=endpoint, -) -vllm.invoke("Who is the first president of United States?") +```bash +pip install -U langchain-oracledb ``` -### 3. Use an Embedding Model -You may instantiate the OCI Data Science model with the `OCIModelDeploymentEndpointEmbeddings`. - -```python -from langchain_oci.embeddings import OCIModelDeploymentEndpointEmbeddings - -# Create an instance of OCI Model Deployment Endpoint -# Replace the endpoint uri with your own -endpoint = "https://modeldeployment..oci.customer-oci.com//predict" - -embeddings = OCIModelDeploymentEndpointEmbeddings( - endpoint=endpoint, -) +--- -query = "Hello World!" -embeddings.embed_query(query) +## Quick Start -documents = ["This is a sample document", "and here is another one"] -embeddings.embed_documents(documents) -``` +This repository includes three main integration categories. For detailed information, please refer to the respective libraries: +- [OCI Generative AI](https://github.com/oracle/langchain-oracle/tree/main/libs/oci) +- [OCI Data Science (Model Deployment)](https://github.com/oracle/langchain-oracle/tree/main/libs/oci) +- [Oracle AI Vector Search](https://github.com/oracle/langchain-oracle/tree/main/libs/oracledb) ## Contributing diff --git a/libs/oracledb/LICENSE b/libs/oracledb/LICENSE new file mode 100644 index 0000000..fc0602f --- /dev/null +++ b/libs/oracledb/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 LangChain, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/oracledb/Makefile b/libs/oracledb/Makefile new file mode 100644 index 0000000..4342953 --- /dev/null +++ b/libs/oracledb/Makefile @@ -0,0 +1,59 @@ +.PHONY: all format lint test tests integration_tests help + +# Default target executed when no arguments are given to make. +all: help + +# Define a variable for the test file path. +TEST_FILE ?= tests/unit_tests/ +integration_test integration_tests: TEST_FILE = tests/integration_tests/ + +test tests integration_test integration_tests: + poetry run pytest $(TEST_FILE) + +test_watch: + poetry run ptw --snapshot-update --now . -- -vv $(TEST_FILE) + +###################### +# LINTING AND FORMATTING +###################### + +# Define a variable for Python and notebook files. +PYTHON_FILES=. +MYPY_CACHE=.mypy_cache +lint format: PYTHON_FILES=. +lint_diff format_diff: PYTHON_FILES=$(shell git diff --relative=libs/oracledb --name-only --diff-filter=d main | grep -E '\.py$$|\.ipynb$$') +lint_package: PYTHON_FILES=langchain_oracledb +lint_tests: PYTHON_FILES=tests +lint_tests: MYPY_CACHE=.mypy_cache_test + +lint lint_diff lint_package lint_tests: + poetry run ruff . + poetry run ruff format $(PYTHON_FILES) --diff + poetry run ruff --select I $(PYTHON_FILES) + mkdir -p $(MYPY_CACHE); poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE) + +format format_diff: + poetry run ruff format $(PYTHON_FILES) + poetry run ruff --select I --fix $(PYTHON_FILES) + +spell_check: + poetry run codespell --toml pyproject.toml + +spell_fix: + poetry run codespell --toml pyproject.toml -w + +check_imports: $(shell find langchain_oracledb -name '*.py') + poetry run python ./scripts/check_imports.py $^ + +###################### +# HELP +###################### + +help: + @echo '----' + @echo 'check_imports - check imports' + @echo 'format - run code formatters' + @echo 'lint - run linters' + @echo 'test - run unit tests' + @echo 'tests - run unit tests' + @echo 'test TEST_FILE= - run all tests in file' diff --git a/libs/oracledb/README.md b/libs/oracledb/README.md new file mode 100644 index 0000000..298e010 --- /dev/null +++ b/libs/oracledb/README.md @@ -0,0 +1,278 @@ +# langchain-oracledb + +This package contains the LangChain integrations with [Oracle AI Vector Search](https://www.oracle.com/database/ai-vector-search/). + +## Installation + +```bash +pip install -U langchain-oracledb +``` + +## Examples + +The following examples showcase basic usage of the components provided by `langchain-oracledb`. + +Please refer to our complete demo guide [Oracle AI Vector Search End-to-End Demo Guide](https://github.com/langchain-ai/langchain/tree/master/cookbook/oracleai_demo.ipynb) to build an end to end RAG pipeline with the help of Oracle AI Vector Search. + +### Connect to Oracle Database + +Some examples below require a connection with Oracle Database through `python-oracledb`. The following sample code will show how to connect to Oracle Database. By default, `python-oracledb` runs in a โ€˜Thinโ€™ mode which connects directly to Oracle Database. This mode does not need Oracle Client libraries. However, some additional functionality is available when python-oracledb uses them. Python-oracledb is said to be in โ€˜Thickโ€™ mode when Oracle Client libraries are used. Both modes have comprehensive functionality supporting the Python Database API v2.0 Specification. See the following [guide](https://python-oracledb.readthedocs.io/en/latest/user_guide/appendix_a.html#featuresummary) that talks about features supported in each mode. You might want to switch to thick-mode if you are unable to use thin-mode. + +```python +import sys +import oracledb + +# please update with your username, password, hostname and service_name +username = "" +password = "" +dsn = "/" + +try: + conn = oracledb.connect(user=username, password=password, dsn=dsn) + print("Connection successful!") +except Exception as e: + print("Connection failed!") + sys.exit(1) +``` + +### Vector Stores + +#### OracleVS + +Use Oracle Vector Database with `OracleVS`. More information can be found in [Oracle AI Vector Search: Vector Store](https://python.langchain.com/docs/integrations/vectorstores/oracle/) documentation. + + +```python +from langchain_oracledb.vectorstores import OracleVS +from langchain_oracledb.vectorstores.oraclevs import create_index + +from langchain_community.embeddings import HuggingFaceEmbeddings +from langchain_community.vectorstores.utils import DistanceStrategy + +embedding_model = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" +) +vector_store = OracleVS(conn, embedding_model, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE) + +# Add texts to the vector database +texts = ["A tablespace can be online (accessible) or offline (not accessible) whenever the database is open.\nA tablespace is usually online so that its data is available to users. The SYSTEM tablespace and temporary tablespaces cannot be taken offline.", "The database stores LOBs differently from other data types. Creating a LOB column implicitly creates a LOB segment and a LOB index. "] +metadata = [ + {"id": "100", "link": "Document Example Test 1"}, + {"id": "101", "link": "Document Example Test 2"}, +] + +vector_store.add_texts(texts, metadata) + +create_index( + conn, vector_store, params={"idx_name": "hnsw_oravs", "idx_type": "HNSW"} +) + +# Perform siliarity search +vs.similarity_search("How does a database stores LOBs?", 1) + +``` + +### Document Loaders + +#### OracleDocLoader + +Load your documents using `OracleDocLoader`. More information can be found in [Oracle AI Vector Search: Document Processing](https://python.langchain.com/docs/integrations/document_loaders/oracleai/) documentation. + +```python +from langchain_oracledb.document_loaders.oracleai import OracleDocLoader + +""" +# loading a local file +loader_params = {} +loader_params["file"] = "" + +# loading from a local directory +loader_params = {} +loader_params["dir"] = "" +""" + +# loading from Oracle Database table +loader_params = { + "owner": "", + "tablename": "demo_tab", + "colname": "data", +} + +""" load the docs """ +loader = OracleDocLoader(conn=conn, params=loader_params) +docs = loader.load() + +""" verify """ +print(f"Number of docs loaded: {len(docs)}") +``` + +#### OracleTextSplitter + +Chunk your documents using `OracleTextSplitter`. More information can be found in [Oracle AI Vector Search: Document Processing](https://python.langchain.com/docs/integrations/document_loaders/oracleai/) documentation. + +```python +from langchain_oracledb.document_loaders.oracleai import OracleTextSplitter +from langchain_oracledb.document_loaders.oracleai import OracleDocLoader + +# loading from Oracle Database table +loader_params = { + "owner": "", + "tablename": "demo_tab", + "colname": "data", +} + +""" load the docs """ +loader = OracleDocLoader(conn=conn, params=loader_params) +docs = loader.load() + +""" +# Some examples +# split by chars, max 500 chars +splitter_params = {"split": "chars", "max": 500, "normalize": "all"} + +# split by words, max 100 words +splitter_params = {"split": "words", "max": 100, "normalize": "all"} + +# split by sentence, max 20 sentences +splitter_params = {"split": "sentence", "max": 20, "normalize": "all"} +""" + +# split by default parameters +splitter_params = {"normalize": "all"} + +# get the splitter instance +splitter = OracleTextSplitter(conn=conn, params=splitter_params) + +list_chunks = [] +for doc in docs: + chunks = splitter.split_text(doc.page_content) + list_chunks.extend(chunks) + +""" verify """ +print(f"Number of Chunks: {len(list_chunks)}") +# print(f"Chunk-0: {list_chunks[0]}") # content +``` + +#### OracleAutonomousDatabaseLoader + +Load documents from Oracle Autonomous Database using `OracleAutonomousDatabaseLoader`. More information can be found in [Oracle Autonomous Database](https://python.langchain.com/docs/integrations/document_loaders/oracleadb_loader/) documentation. + +```python +from langchain_oracledb.document_loaders import OracleAutonomousDatabaseLoader +from settings import s + +SQL_QUERY = "select prod_id, time_id from sh.costs fetch first 5 rows only" + +doc_loader_1 = OracleAutonomousDatabaseLoader( + query=SQL_QUERY, + user=s.USERNAME, + password=s.PASSWORD, + schema=s.SCHEMA, + config_dir=s.CONFIG_DIR, + wallet_location=s.WALLET_LOCATION, + wallet_password=s.PASSWORD, + tns_name=s.TNS_NAME, +) +doc_1 = doc_loader_1.load() + +doc_loader_2 = OracleAutonomousDatabaseLoader( + query=SQL_QUERY, + user=s.USERNAME, + password=s.PASSWORD, + schema=s.SCHEMA, + connection_string=s.CONNECTION_STRING, + wallet_location=s.WALLET_LOCATION, + wallet_password=s.PASSWORD, +) +doc_2 = doc_loader_2.load() +``` + +### Embeddings + +#### OracleEmbeddings + +Generate embeddings for your documents using `OracleEmbeddings`. More information can be found in [Oracle AI Vector Search: Generate Embeddings](https://python.langchain.com/docs/integrations/text_embedding/oracleai/) documentation. + +```python +from langchain_oracledb.embeddings.oracleai import OracleEmbeddings + +""" +# using ocigenai +embedder_params = { + "provider": "ocigenai", + "credential_name": "OCI_CRED", + "url": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/embedText", + "model": "cohere.embed-english-light-v3.0", +} + +# using huggingface +embedder_params = { + "provider": "huggingface", + "credential_name": "HF_CRED", + "url": "https://api-inference.huggingface.co/pipeline/feature-extraction/", + "model": "sentence-transformers/all-MiniLM-L6-v2", + "wait_for_model": "true" +} +""" + +# using ONNX model loaded to Oracle Database +embedder_params = {"provider": "database", "model": "demo_model"} + +# If a proxy is not required for your environment, you can omit the 'proxy' parameter below +embedder = OracleEmbeddings(conn=conn, params=embedder_params, proxy=proxy) +embed = embedder.embed_query("Hello World!") + +""" verify """ +print(f"Embedding generated by OracleEmbeddings: {embed}") +``` + +### Utilities + +#### OracleSummary + +Generate summary for your documents using `OracleSummary`. More information can be found in [Oracle AI Vector Search: Generate Summary](https://python.langchain.com/docs/integrations/tools/oracleai/) documentation. + +```python +from langchain_oracledb.utilities.oracleai import OracleSummary +from langchain_core.documents import Document + +""" +# using 'ocigenai' provider +summary_params = { + "provider": "ocigenai", + "credential_name": "OCI_CRED", + "url": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/summarizeText", + "model": "cohere.command", +} + +# using 'huggingface' provider +summary_params = { + "provider": "huggingface", + "credential_name": "HF_CRED", + "url": "https://api-inference.huggingface.co/models/", + "model": "facebook/bart-large-cnn", + "wait_for_model": "true" +} +""" + +# using 'database' provider +summary_params = { + "provider": "database", + "glevel": "S", + "numParagraphs": 1, + "language": "english", +} + +# get the summary instance +# Remove proxy if not required +summ = OracleSummary(conn=conn, params=summary_params, proxy=proxy) +summary = summ.get_summary( + "In the heart of the forest, " + + "a lone fox ventured out at dusk, seeking a lost treasure. " + + "With each step, memories flooded back, guiding its path. " + + "As the moon rose high, illuminating the night, the fox unearthed " + + "not gold, but a forgotten friendship, worth more than any riches." +) + +print(f"Summary generated by OracleSummary: {summary}") +``` diff --git a/libs/oracledb/langchain_oracledb/__init__.py b/libs/oracledb/langchain_oracledb/__init__.py new file mode 100644 index 0000000..005c671 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/__init__.py @@ -0,0 +1,22 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from langchain_oracledb.document_loaders.oracleadb_loader import ( + OracleAutonomousDatabaseLoader, +) +from langchain_oracledb.document_loaders.oracleai import ( + OracleDocLoader, + OracleTextSplitter, +) +from langchain_oracledb.embeddings.oracleai import OracleEmbeddings +from langchain_oracledb.utilities.oracleai import OracleSummary +from langchain_oracledb.vectorstores.oraclevs import OracleVS + +__all__ = [ + "OracleDocLoader", + "OracleTextSplitter", + "OracleAutonomousDatabaseLoader", + "OracleEmbeddings", + "OracleSummary", + "OracleVS", +] diff --git a/libs/oracledb/langchain_oracledb/document_loaders/__init__.py b/libs/oracledb/langchain_oracledb/document_loaders/__init__.py new file mode 100644 index 0000000..e640ac0 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/document_loaders/__init__.py @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from langchain_oracledb.document_loaders.oracleadb_loader import ( + OracleAutonomousDatabaseLoader, +) +from langchain_oracledb.document_loaders.oracleai import ( + OracleDocLoader, + OracleTextSplitter, +) + +__all__ = ["OracleDocLoader", "OracleTextSplitter", "OracleAutonomousDatabaseLoader"] diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py new file mode 100644 index 0000000..f0f1fe1 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py @@ -0,0 +1,134 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from typing import Any, Dict, List, Optional, Union + +import oracledb +from langchain_community.document_loaders.base import BaseLoader +from langchain_core.documents import Document + + +class OracleAutonomousDatabaseLoader(BaseLoader): + """ + Load from oracle adb + + Autonomous Database connection can be made by either connection_string + or tns name. wallet_location and wallet_password are required + for TLS connection. + Each document will represent one row of the query result. + Columns are written into the `page_content` and 'metadata' in + constructor is written into 'metadata' of document, + by default, the 'metadata' is None. + """ + + def __init__( + self, + query: str, + user: str, + password: str, + *, + schema: Optional[str] = None, + tns_name: Optional[str] = None, + config_dir: Optional[str] = None, + wallet_location: Optional[str] = None, + wallet_password: Optional[str] = None, + connection_string: Optional[str] = None, + metadata: Optional[List[str]] = None, + parameters: Optional[Union[list, tuple, dict]] = None, + ): + """ + init method + :param query: sql query to execute + :param user: username + :param password: user password + :param schema: schema to run in database + :param tns_name: tns name in tnsname.ora + :param config_dir: directory of config files(tnsname.ora, wallet) + :param wallet_location: location of wallet + :param wallet_password: password of wallet + :param connection_string: connection string to connect to adb instance + :param metadata: metadata used in document + :param parameters: bind variable to use in query + """ + # Mandatory required arguments. + self.query = query + self.user = user + self.password = password + + # Schema + self.schema = schema + + # TNS connection Method + self.tns_name = tns_name + self.config_dir = config_dir + + # Wallet configuration is required for mTLS connection + self.wallet_location = wallet_location + self.wallet_password = wallet_password + + # Connection String connection method + self.connection_string = connection_string + + # metadata column + self.metadata = metadata + + # parameters, e.g bind variable + self.parameters = parameters + + # dsn + self.dsn: Optional[str] + self._set_dsn() + + def _set_dsn(self) -> None: + if self.connection_string: + self.dsn = self.connection_string + elif self.tns_name: + self.dsn = self.tns_name + + def _run_query(self) -> List[Dict[str, Any]]: + connect_param = {"user": self.user, "password": self.password, "dsn": self.dsn} + if self.dsn == self.tns_name: + connect_param["config_dir"] = self.config_dir + if self.wallet_location and self.wallet_password: + connect_param["wallet_location"] = self.wallet_location + connect_param["wallet_password"] = self.wallet_password + + try: + connection = oracledb.connect(**connect_param) + cursor = connection.cursor() + if self.schema: + cursor.execute(f"alter session set current_schema={self.schema}") + if self.parameters: + cursor.execute(self.query, self.parameters) + else: + cursor.execute(self.query) + columns = [col[0] for col in cursor.description] + data = cursor.fetchall() + data = [ + { + i: (j if not isinstance(j, oracledb.LOB) else j.read()) + for i, j in zip(columns, row) + } + for row in data + ] + except oracledb.DatabaseError as e: + print("Got error while connecting: " + str(e)) # noqa: T201 + data = [] + finally: + cursor.close() + connection.close() + + return data + + def load(self) -> List[Document]: + data = self._run_query() + documents = [] + metadata_columns = self.metadata if self.metadata else [] + for row in data: + metadata = { + key: value for key, value in row.items() if key in metadata_columns + } + doc = Document(page_content=str(row), metadata=metadata) + documents.append(doc) + + return documents diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py new file mode 100644 index 0000000..e03d7d4 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py @@ -0,0 +1,422 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +# Authors: +# Harichandan Roy (hroy) +# David Jiang (ddjiang) +# +# ----------------------------------------------------------------------------- +# oracleai.py +# ----------------------------------------------------------------------------- + +from __future__ import annotations + +import hashlib +import json +import logging +import os +import random +import struct +import time +import traceback +from html.parser import HTMLParser +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +from langchain_core.document_loaders import BaseLoader +from langchain_core.documents import Document +from langchain_text_splitters import TextSplitter + +if TYPE_CHECKING: + from oracledb import Connection + +import oracledb + +logger = logging.getLogger(__name__) + +"""ParseOracleDocMetadata class""" + + +class ParseOracleDocMetadata(HTMLParser): + """Parse Oracle doc metadata...""" + + def __init__(self) -> None: + super().__init__() + self.reset() + self.match = False + self.metadata: Dict[str, Any] = {} + + def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None: + if tag == "meta": + entry: Optional[str] = "" + for name, value in attrs: + if name == "name": + entry = value + if name == "content": + if entry: + self.metadata[entry] = value + elif tag == "title": + self.match = True + + def handle_data(self, data: str) -> None: + if self.match: + self.metadata["title"] = data + self.match = False + + def get_metadata(self) -> Dict[str, Any]: + return self.metadata + + +"""OracleDocReader class""" + + +class OracleDocReader: + """Read a file""" + + @staticmethod + def generate_object_id(input_string: Union[str, None] = None) -> str: + out_length = 32 # output length + hash_len = 8 # hash value length + + if input_string is None: + input_string = "".join( + random.choices( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + k=16, + ) + ) + + # timestamp + timestamp = int(time.time()) + timestamp_bin = struct.pack(">I", timestamp) # 4 bytes + + # hash_value + hashval_bin = hashlib.sha256(input_string.encode()).digest() + hashval_bin = hashval_bin[:hash_len] # 8 bytes + + # counter + counter_bin = struct.pack(">I", random.getrandbits(32)) # 4 bytes + + # binary object id + object_id = timestamp_bin + hashval_bin + counter_bin # 16 bytes + object_id_hex = object_id.hex() # 32 bytes + object_id_hex = object_id_hex.zfill( + out_length + ) # fill with zeros if less than 32 bytes + + object_id_hex = object_id_hex[:out_length] + + return object_id_hex + + @staticmethod + def read_file( + conn: Connection, file_path: str, params: dict + ) -> Union[Document, None]: + """Read a file using OracleReader + Args: + conn: Oracle Connection, + file_path: Oracle Directory, + params: ONNX file name. + Returns: + Plain text and metadata as Langchain Document. + """ + + metadata: Dict[str, Any] = {} + try: + oracledb.defaults.fetch_lobs = False + cursor = conn.cursor() + + with open(file_path, "rb") as f: + data = f.read() + + if data is None: + return Document(page_content="", metadata=metadata) + + mdata = cursor.var(oracledb.DB_TYPE_CLOB) + text = cursor.var(oracledb.DB_TYPE_CLOB) + cursor.execute( + """ + declare + input blob; + begin + input := :blob; + :mdata := dbms_vector_chain.utl_to_text(input, json(:pref)); + :text := dbms_vector_chain.utl_to_text(input); + end;""", + blob=data, + pref=json.dumps(params), + mdata=mdata, + text=text, + ) + cursor.close() + + if mdata is None: + metadata = {} + else: + doc_data = str(mdata.getvalue()) + if doc_data.startswith("" + ): + p = ParseOracleDocMetadata() + p.feed(doc_data) + metadata = p.get_metadata() + + doc_id = OracleDocReader.generate_object_id(conn.username + "$" + file_path) + metadata["_oid"] = doc_id + metadata["_file"] = file_path + + if text is None: + return Document(page_content="", metadata=metadata) + else: + return Document(page_content=str(text.getvalue()), metadata=metadata) + + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + logger.info(f"Skip processing {file_path}") + cursor.close() + return None + + +"""OracleDocLoader class""" + + +class OracleDocLoader(BaseLoader): + """Read documents using OracleDocLoader + Args: + conn: Oracle Connection, + params: Loader parameters. + """ + + def __init__(self, conn: Connection, params: Dict[str, Any], **kwargs: Any): + self.conn = conn + self.params = json.loads(json.dumps(params)) + super().__init__(**kwargs) + + def load(self) -> List[Document]: + """Load data into LangChain Document objects...""" + + ncols = 0 + results: List[Document] = [] + metadata: Dict[str, Any] = {} + m_params = {"plaintext": "false"} + try: + # extract the parameters + if self.params is not None: + self.file = self.params.get("file") + self.dir = self.params.get("dir") + self.owner = self.params.get("owner") + self.tablename = self.params.get("tablename") + self.colname = self.params.get("colname") + else: + raise Exception("Missing loader parameters") + + oracledb.defaults.fetch_lobs = False + + if self.file: + doc = OracleDocReader.read_file(self.conn, self.file, m_params) + + if doc is None: + return results + + results.append(doc) + + if self.dir: + skip_count = 0 + for file_name in os.listdir(self.dir): + file_path = os.path.join(self.dir, file_name) + if os.path.isfile(file_path): + doc = OracleDocReader.read_file(self.conn, file_path, m_params) + + if doc is None: + skip_count = skip_count + 1 + logger.info(f"Total skipped: {skip_count}\n") + else: + results.append(doc) + + if self.tablename: + try: + if self.owner is None or self.colname is None: + raise Exception("Missing owner or column name or both.") + + cursor = self.conn.cursor() + self.mdata_cols = self.params.get("mdata_cols") + if self.mdata_cols is not None: + if len(self.mdata_cols) > 3: + raise Exception( + "Exceeds the max number of columns " + + "you can request for metadata." + ) + + # execute a query to get column data types + sql = ( + "select column_name, data_type from all_tab_columns " + + "where owner = :ownername and " + + "table_name = :tablename" + ) + cursor.execute( + sql, + ownername=self.owner.upper(), + tablename=self.tablename.upper(), + ) + + # cursor.execute(sql) + rows = cursor.fetchall() + for row in rows: + if row[0] in self.mdata_cols: + if row[1] not in [ + "NUMBER", + "BINARY_DOUBLE", + "BINARY_FLOAT", + "LONG", + "DATE", + "TIMESTAMP", + "VARCHAR2", + ]: + raise Exception( + "The datatype for the column requested " + + "for metadata is not supported." + ) + + self.mdata_cols_sql = ", rowid" + if self.mdata_cols is not None: + for col in self.mdata_cols: + self.mdata_cols_sql = self.mdata_cols_sql + ", " + col + + # [TODO] use bind variables + sql = ( + "select dbms_vector_chain.utl_to_text(t." + + self.colname + + ", json('" + + json.dumps(m_params) + + "')) mdata, dbms_vector_chain.utl_to_text(t." + + self.colname + + ") text" + + self.mdata_cols_sql + + " from " + + self.owner + + "." + + self.tablename + + " t" + ) + + cursor.execute(sql) + for row in cursor: + metadata = {} + + if row is None: + doc_id = OracleDocReader.generate_object_id( + self.conn.username + + "$" + + self.owner + + "$" + + self.tablename + + "$" + + self.colname + ) + metadata["_oid"] = doc_id + results.append(Document(page_content="", metadata=metadata)) + else: + if row[0] is not None: + data = str(row[0]) + if data.startswith("" + ): + p = ParseOracleDocMetadata() + p.feed(data) + metadata = p.get_metadata() + + doc_id = OracleDocReader.generate_object_id( + self.conn.username + + "$" + + self.owner + + "$" + + self.tablename + + "$" + + self.colname + + "$" + + str(row[2]) + ) + metadata["_oid"] = doc_id + metadata["_rowid"] = row[2] + + # process projected metadata cols + if self.mdata_cols is not None: + ncols = len(self.mdata_cols) + + for i in range(0, ncols): + metadata[self.mdata_cols[i]] = row[i + 2] + + if row[1] is None: + results.append( + Document(page_content="", metadata=metadata) + ) + else: + results.append( + Document( + page_content=str(row[1]), metadata=metadata + ) + ) + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + traceback.print_exc() + cursor.close() + raise + + return results + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + traceback.print_exc() + raise + + +class OracleTextSplitter(TextSplitter): + """Splitting text using Oracle chunker.""" + + def __init__(self, conn: Connection, params: Dict[str, Any], **kwargs: Any) -> None: + """Initialize.""" + self.conn = conn + self.params = params + super().__init__(**kwargs) + try: + import json + + self._oracledb = oracledb + self._json = json + except ImportError: + raise ImportError( + "oracledb or json or both are not installed. " + + "Please install them. " + + "Recommendations: `pip install oracledb`. " + ) + + def split_text(self, text: str) -> List[str]: + """Split incoming text and return chunks.""" + + splits = [] + + try: + # returns strings or bytes instead of a locator + self._oracledb.defaults.fetch_lobs = False + + cursor = self.conn.cursor() + + cursor.setinputsizes(content=oracledb.CLOB) + cursor.execute( + "select t.column_value from " + + "dbms_vector_chain.utl_to_chunks(:content, json(:params)) t", + content=text, + params=self._json.dumps(self.params), + ) + + while True: + row = cursor.fetchone() + if row is None: + break + d = self._json.loads(row[0]) + splits.append(d["chunk_data"]) + + return splits + + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + traceback.print_exc() + raise diff --git a/libs/oracledb/langchain_oracledb/embeddings/__init__.py b/libs/oracledb/langchain_oracledb/embeddings/__init__.py new file mode 100644 index 0000000..8b89cf7 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/embeddings/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from langchain_oracledb.embeddings.oracleai import OracleEmbeddings + +__all__ = ["OracleEmbeddings"] diff --git a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py new file mode 100644 index 0000000..c7f9dc6 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py @@ -0,0 +1,191 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +# Authors: +# Harichandan Roy (hroy) +# David Jiang (ddjiang) +# +# ----------------------------------------------------------------------------- +# oracleai.py +# ----------------------------------------------------------------------------- + +from __future__ import annotations + +import json +import logging +import traceback +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from langchain_core.embeddings import Embeddings +from pydantic import BaseModel, ConfigDict + +if TYPE_CHECKING: + from oracledb import Connection + +import oracledb + +logger = logging.getLogger(__name__) + +"""OracleEmbeddings class""" + + +class OracleEmbeddings(BaseModel, Embeddings): + """Get Embeddings""" + + """Oracle Connection""" + conn: Any = None + """Embedding Parameters""" + params: Dict[str, Any] + """Proxy""" + proxy: Optional[str] = None + + def __init__(self, **kwargs: Any): + super().__init__(**kwargs) + + model_config = ConfigDict( + extra="forbid", + ) + + """ + 1 - user needs to have create procedure, + create mining model, create any directory privilege. + 2 - grant create procedure, create mining model, + create any directory to ; + """ + + @staticmethod + def load_onnx_model( + conn: Connection, dir: str, onnx_file: str, model_name: str + ) -> None: + """Load an ONNX model to Oracle Database. + Args: + conn: Oracle Connection, + dir: Oracle Directory, + onnx_file: ONNX file name, + model_name: Name of the model. + """ + + try: + if conn is None or dir is None or onnx_file is None or model_name is None: + raise Exception("Invalid input") + + cursor = conn.cursor() + cursor.execute( + """ + begin + dbms_data_mining.drop_model(model_name => :model, force => true); + SYS.DBMS_VECTOR.load_onnx_model(:path, :filename, :model, + json('{"function" : "embedding", + "embeddingOutput" : "embedding", + "input": {"input": ["DATA"]}}')); + end;""", + path=dir, + filename=onnx_file, + model=model_name, + ) + + cursor.close() + + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + traceback.print_exc() + cursor.close() + raise + + def embed_documents(self, texts: List[str]) -> List[List[float]]: + """Compute doc embeddings using an OracleEmbeddings. + Args: + texts: The list of texts to embed. + Returns: + List of embeddings, one for each input text. + """ + + if texts is None: + return None + + embeddings: List[List[float]] = [] + try: + # returns strings or bytes instead of a locator + oracledb.defaults.fetch_lobs = False + cursor = self.conn.cursor() + + if self.proxy: + cursor.execute( + "begin utl_http.set_proxy(:proxy); end;", proxy=self.proxy + ) + + chunks = [] + for i, text in enumerate(texts, start=1): + chunk = {"chunk_id": i, "chunk_data": text} + chunks.append(json.dumps(chunk)) + + vector_array_type = self.conn.gettype("SYS.VECTOR_ARRAY_T") + inputs = vector_array_type.newobject(chunks) + cursor.execute( + "select t.* " + + "from dbms_vector_chain.utl_to_embeddings(:content, " + + "json(:params)) t", + content=inputs, + params=json.dumps(self.params), + ) + + for row in cursor: + if row is None: + embeddings.append([]) + else: + rdata = json.loads(row[0]) + # dereference string as array + vec = json.loads(rdata["embed_vector"]) + embeddings.append(vec) + + cursor.close() + return embeddings + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + traceback.print_exc() + cursor.close() + raise + + def embed_query(self, text: str) -> List[float]: + """Compute query embedding using an OracleEmbeddings. + Args: + text: The text to embed. + Returns: + Embedding for the text. + """ + return self.embed_documents([text])[0] + + +# uncomment the following code block to run the test + +""" +# A sample unit test. + +import oracledb +# get the Oracle connection +conn = oracledb.connect( + user="", + password="", + dsn="/", +) +print("Oracle connection is established...") + +# params +embedder_params = {"provider": "database", "model": "demo_model"} +proxy = "" + +# instance +embedder = OracleEmbeddings(conn=conn, params=embedder_params, proxy=proxy) + +docs = ["hello world!", "hi everyone!", "greetings!"] +embeds = embedder.embed_documents(docs) +print(f"Total Embeddings: {len(embeds)}") +print(f"Embedding generated by OracleEmbeddings: {embeds[0]}\n") + +embed = embedder.embed_query("Hello World!") +print(f"Embedding generated by OracleEmbeddings: {embed}") + +conn.close() +print("Connection is closed.") + +""" diff --git a/libs/oracledb/langchain_oracledb/utilities/__init__.py b/libs/oracledb/langchain_oracledb/utilities/__init__.py new file mode 100644 index 0000000..5dab6b1 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/utilities/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from langchain_oracledb.utilities.oracleai import OracleSummary + +__all__ = ["OracleSummary"] diff --git a/libs/oracledb/langchain_oracledb/utilities/oracleai.py b/libs/oracledb/langchain_oracledb/utilities/oracleai.py new file mode 100644 index 0000000..3d7a942 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/utilities/oracleai.py @@ -0,0 +1,198 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +# Authors: +# Harichandan Roy (hroy) +# David Jiang (ddjiang) +# +# ----------------------------------------------------------------------------- +# oracleai.py +# ----------------------------------------------------------------------------- + +from __future__ import annotations + +import json +import logging +import traceback +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from langchain_core.documents import Document + +if TYPE_CHECKING: + from oracledb import Connection + +import oracledb + +logger = logging.getLogger(__name__) + +"""OracleSummary class""" + + +class OracleSummary: + """Get Summary + Args: + conn: Oracle Connection, + params: Summary parameters, + proxy: Proxy + """ + + def __init__( + self, conn: Connection, params: Dict[str, Any], proxy: Optional[str] = None + ): + self.conn = conn + self.proxy = proxy + self.summary_params = params + + def get_summary(self, docs: Any) -> List[str]: + """Get the summary of the input docs. + Args: + docs: The documents to generate summary for. + Allowed input types: str, Document, List[str], List[Document] + Returns: + List of summary text, one for each input doc. + """ + + if docs is None: + return [] + + results: List[str] = [] + try: + oracledb.defaults.fetch_lobs = False + cursor = self.conn.cursor() + + if self.proxy: + cursor.execute( + "begin utl_http.set_proxy(:proxy); end;", proxy=self.proxy + ) + + if isinstance(docs, str): + results = [] + + summary = cursor.var(oracledb.DB_TYPE_CLOB) + cursor.execute( + """ + declare + input clob; + begin + input := :data; + :summ := dbms_vector_chain.utl_to_summary(input, json(:params)); + end;""", + data=docs, + params=json.dumps(self.summary_params), + summ=summary, + ) + + if summary is None: + results.append("") + else: + results.append(str(summary.getvalue())) + + elif isinstance(docs, Document): + results = [] + + summary = cursor.var(oracledb.DB_TYPE_CLOB) + cursor.execute( + """ + declare + input clob; + begin + input := :data; + :summ := dbms_vector_chain.utl_to_summary(input, json(:params)); + end;""", + data=docs.page_content, + params=json.dumps(self.summary_params), + summ=summary, + ) + + if summary is None: + results.append("") + else: + results.append(str(summary.getvalue())) + + elif isinstance(docs, List): + results = [] + + for doc in docs: + summary = cursor.var(oracledb.DB_TYPE_CLOB) + if isinstance(doc, str): + cursor.execute( + """ + declare + input clob; + begin + input := :data; + :summ := dbms_vector_chain.utl_to_summary(input, + json(:params)); + end;""", + data=doc, + params=json.dumps(self.summary_params), + summ=summary, + ) + + elif isinstance(doc, Document): + cursor.execute( + """ + declare + input clob; + begin + input := :data; + :summ := dbms_vector_chain.utl_to_summary(input, + json(:params)); + end;""", + data=doc.page_content, + params=json.dumps(self.summary_params), + summ=summary, + ) + + else: + raise Exception("Invalid input type") + + if summary is None: + results.append("") + else: + results.append(str(summary.getvalue())) + + else: + raise Exception("Invalid input type") + + cursor.close() + return results + + except Exception as ex: + logger.info(f"An exception occurred :: {ex}") + traceback.print_exc() + cursor.close() + raise + + +# uncomment the following code block to run the test + +""" +# A sample unit test. + +''' get the Oracle connection ''' +conn = oracledb.connect( + user="", + password="", + dsn="") +print("Oracle connection is established...") + +''' params ''' +summary_params = {"provider": "database","glevel": "S", + "numParagraphs": 1,"language": "english"} +proxy = "" + +''' instance ''' +summ = OracleSummary(conn=conn, params=summary_params, proxy=proxy) + +summary = summ.get_summary("In the heart of the forest, " + + "a lone fox ventured out at dusk, seeking a lost treasure. " + + "With each step, memories flooded back, guiding its path. " + + "As the moon rose high, illuminating the night, the fox unearthed " + + "not gold, but a forgotten friendship, worth more than any riches.") +print(f"Summary generated by OracleSummary: {summary}") + +conn.close() +print("Connection is closed.") + +""" diff --git a/libs/oracledb/langchain_oracledb/vectorstores/__init__.py b/libs/oracledb/langchain_oracledb/vectorstores/__init__.py new file mode 100644 index 0000000..becbe5e --- /dev/null +++ b/libs/oracledb/langchain_oracledb/vectorstores/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from langchain_oracledb.vectorstores.oraclevs import OracleVS + +__all__ = ["OracleVS"] diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py new file mode 100644 index 0000000..3ddd8c9 --- /dev/null +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -0,0 +1,1049 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +from __future__ import annotations + +import array +import functools +import hashlib +import json +import logging +import os +import uuid +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + cast, +) + +from numpy.typing import NDArray + +if TYPE_CHECKING: + from oracledb import Connection + +import numpy as np +import oracledb +from langchain_community.vectorstores.utils import ( + DistanceStrategy, + maximal_marginal_relevance, +) +from langchain_core.documents import Document +from langchain_core.embeddings import Embeddings +from langchain_core.vectorstores import VectorStore + +logger = logging.getLogger(__name__) +log_level = os.getenv("LOG_LEVEL", "ERROR").upper() +logging.basicConfig( + level=getattr(logging, log_level), + format="%(asctime)s - %(levelname)s - %(message)s", +) + + +# Define a type variable that can be any kind of function +T = TypeVar("T", bound=Callable[..., Any]) + + +def _get_connection(client: Any) -> Connection | None: + # check if ConnectionPool exists + connection_pool_class = getattr(oracledb, "ConnectionPool", None) + + if isinstance(client, oracledb.Connection): + return client + elif connection_pool_class and isinstance(client, connection_pool_class): + return client.acquire() + else: + valid_types = "oracledb.Connection" + if connection_pool_class: + valid_types += " or oracledb.ConnectionPool" + raise TypeError( + f"Expected client of type {valid_types}, got {type(client).__name__}" + ) + + +def _handle_exceptions(func: T) -> T: + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + try: + return func(*args, **kwargs) + except RuntimeError as db_err: + # Handle a known type of error (e.g., DB-related) specifically + logger.exception("DB-related error occurred.") + raise RuntimeError( + "Failed due to a DB issue: {}".format(db_err) + ) from db_err + except ValueError as val_err: + # Handle another known type of error specifically + logger.exception("Validation error.") + raise ValueError("Validation failed: {}".format(val_err)) from val_err + except Exception as e: + # Generic handler for all other exceptions + logger.exception("An unexpected error occurred: {}".format(e)) + raise RuntimeError("Unexpected error: {}".format(e)) from e + + return cast(T, wrapper) + + +def _table_exists(connection: Connection, table_name: str) -> bool: + try: + with connection.cursor() as cursor: + cursor.execute(f"SELECT COUNT(*) FROM {table_name}") + return True + except oracledb.DatabaseError as ex: + err_obj = ex.args + if err_obj[0].code == 942: + return False + raise + + +def _compare_version(version: str, target_version: str) -> bool: + # Split both version strings into parts + version_parts = [int(part) for part in version.split(".")] + target_parts = [int(part) for part in target_version.split(".")] + + # Compare each part + for v, t in zip(version_parts, target_parts): + if v < t: + return True # Current version is less + elif v > t: + return False # Current version is greater + + # If all parts equal so far, check if version has fewer parts than target_version + return len(version_parts) < len(target_parts) + + +@_handle_exceptions +def _index_exists(connection: Connection, index_name: str) -> bool: + # Check if the index exists + query = """ + SELECT index_name + FROM all_indexes + WHERE upper(index_name) = upper(:idx_name) + """ + + with connection.cursor() as cursor: + # Execute the query + cursor.execute(query, idx_name=index_name.upper()) + result = cursor.fetchone() + + # Check if the index exists + return result is not None + + +def _get_distance_function(distance_strategy: DistanceStrategy) -> str: + # Dictionary to map distance strategies to their corresponding function + # names + distance_strategy2function = { + DistanceStrategy.EUCLIDEAN_DISTANCE: "EUCLIDEAN", + DistanceStrategy.DOT_PRODUCT: "DOT", + DistanceStrategy.COSINE: "COSINE", + } + + # Attempt to return the corresponding distance function + if distance_strategy in distance_strategy2function: + return distance_strategy2function[distance_strategy] + + # If it's an unsupported distance strategy, raise an error + raise ValueError(f"Unsupported distance strategy: {distance_strategy}") + + +def _get_index_name(base_name: str) -> str: + unique_id = str(uuid.uuid4()).replace("-", "") + return f"{base_name}_{unique_id}" + + +@_handle_exceptions +def _create_table(connection: Connection, table_name: str, embedding_dim: int) -> None: + cols_dict = { + "id": "RAW(16) DEFAULT SYS_GUID() PRIMARY KEY", + "text": "CLOB", + "metadata": "JSON", + "embedding": f"vector({embedding_dim}, FLOAT32)", + } + + if not _table_exists(connection, table_name): + with connection.cursor() as cursor: + ddl_body = ", ".join( + f"{col_name} {col_type}" for col_name, col_type in cols_dict.items() + ) + ddl = f"CREATE TABLE {table_name} ({ddl_body})" + cursor.execute(ddl) + logger.info("Table created successfully...") + else: + logger.info("Table already exists...") + + +@_handle_exceptions +def create_index( + client: Any, + vector_store: OracleVS, + params: Optional[dict[str, Any]] = None, +) -> None: + connection = _get_connection(client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + if params: + if params["idx_type"] == "HNSW": + _create_hnsw_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + elif params["idx_type"] == "IVF": + _create_ivf_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + else: + _create_hnsw_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + else: + _create_hnsw_index( + connection, vector_store.table_name, vector_store.distance_strategy, params + ) + return + + +@_handle_exceptions +def _create_hnsw_index( + connection: Connection, + table_name: str, + distance_strategy: DistanceStrategy, + params: Optional[dict[str, Any]] = None, +) -> None: + defaults = { + "idx_name": "HNSW", + "idx_type": "HNSW", + "neighbors": 32, + "efConstruction": 200, + "accuracy": 90, + "parallel": 8, + } + + if params: + config = params.copy() + # Ensure compulsory parts are included + for compulsory_key in ["idx_name", "parallel"]: + if compulsory_key not in config: + if compulsory_key == "idx_name": + config[compulsory_key] = _get_index_name( + str(defaults[compulsory_key]) + ) + else: + config[compulsory_key] = defaults[compulsory_key] + + # Validate keys in config against defaults + for key in config: + if key not in defaults: + raise ValueError(f"Invalid parameter: {key}") + else: + config = defaults + + # Base SQL statement + idx_name = config["idx_name"] + base_sql = ( + f"create vector index {idx_name} on {table_name}(embedding) " + f"ORGANIZATION INMEMORY NEIGHBOR GRAPH" + ) + + # Optional parts depending on parameters + accuracy_part = " WITH TARGET ACCURACY {accuracy}" if ("accuracy" in config) else "" + distance_part = f" DISTANCE {_get_distance_function(distance_strategy)}" + + parameters_part = "" + if "neighbors" in config and "efConstruction" in config: + parameters_part = ( + " parameters (type {idx_type}, neighbors {" + "neighbors}, efConstruction {efConstruction})" + ) + elif "neighbors" in config and "efConstruction" not in config: + config["efConstruction"] = defaults["efConstruction"] + parameters_part = ( + " parameters (type {idx_type}, neighbors {" + "neighbors}, efConstruction {efConstruction})" + ) + elif "neighbors" not in config and "efConstruction" in config: + config["neighbors"] = defaults["neighbors"] + parameters_part = ( + " parameters (type {idx_type}, neighbors {" + "neighbors}, efConstruction {efConstruction})" + ) + + # Always included part for parallel + parallel_part = " parallel {parallel}" + + # Combine all parts + ddl_assembly = ( + base_sql + accuracy_part + distance_part + parameters_part + parallel_part + ) + # Format the SQL with values from the params dictionary + ddl = ddl_assembly.format(**config) + + # Check if the index exists + if not _index_exists(connection, config["idx_name"]): + with connection.cursor() as cursor: + cursor.execute(ddl) + logger.info("Index created successfully...") + else: + logger.info("Index already exists...") + + +@_handle_exceptions +def _create_ivf_index( + connection: Connection, + table_name: str, + distance_strategy: DistanceStrategy, + params: Optional[dict[str, Any]] = None, +) -> None: + # Default configuration + defaults = { + "idx_name": "IVF", + "idx_type": "IVF", + "neighbor_part": 32, + "accuracy": 90, + "parallel": 8, + } + + if params: + config = params.copy() + # Ensure compulsory parts are included + for compulsory_key in ["idx_name", "parallel"]: + if compulsory_key not in config: + if compulsory_key == "idx_name": + config[compulsory_key] = _get_index_name( + str(defaults[compulsory_key]) + ) + else: + config[compulsory_key] = defaults[compulsory_key] + + # Validate keys in config against defaults + for key in config: + if key not in defaults: + raise ValueError(f"Invalid parameter: {key}") + else: + config = defaults + + # Base SQL statement + idx_name = config["idx_name"] + base_sql = ( + f"CREATE VECTOR INDEX {idx_name} ON {table_name}(embedding) " + f"ORGANIZATION NEIGHBOR PARTITIONS" + ) + + # Optional parts depending on parameters + accuracy_part = " WITH TARGET ACCURACY {accuracy}" if ("accuracy" in config) else "" + distance_part = f" DISTANCE {_get_distance_function(distance_strategy)}" + + parameters_part = "" + if "idx_type" in config and "neighbor_part" in config: + parameters_part = ( + f" PARAMETERS (type {config['idx_type']}, neighbor" + f" partitions {config['neighbor_part']})" + ) + + # Always included part for parallel + parallel_part = f" PARALLEL {config['parallel']}" + + # Combine all parts + ddl_assembly = ( + base_sql + accuracy_part + distance_part + parameters_part + parallel_part + ) + # Format the SQL with values from the params dictionary + ddl = ddl_assembly.format(**config) + + # Check if the index exists + if not _index_exists(connection, config["idx_name"]): + with connection.cursor() as cursor: + cursor.execute(ddl) + logger.info("Index created successfully...") + else: + logger.info("Index already exists...") + + +@_handle_exceptions +def drop_table_purge(client: Any, table_name: str) -> None: + """Drop a table and purge it from the database. + + Args: + client: The OracleDB connection object. + table_name: The name of the table to drop. + + Raises: + RuntimeError: If an error occurs while dropping the table. + """ + connection = _get_connection(client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + if _table_exists(connection, table_name): + with connection.cursor() as cursor: + ddl = f"DROP TABLE {table_name} PURGE" + cursor.execute(ddl) + logger.info("Table dropped successfully...") + else: + logger.info("Table not found...") + return + + +@_handle_exceptions +def drop_index_if_exists(client: Any, index_name: str) -> None: + """Drop an index if it exists. + + Args: + client: The OracleDB connection object. + index_name: The name of the index to drop. + + Raises: + RuntimeError: If an error occurs while dropping the index. + """ + connection = _get_connection(client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + if _index_exists(connection, index_name): + drop_query = f"DROP INDEX {index_name}" + with connection.cursor() as cursor: + cursor.execute(drop_query) + logger.info(f"Index {index_name} has been dropped.") + else: + logger.exception(f"Index {index_name} does not exist.") + return + + +class OracleVS(VectorStore): + """`OracleVS` vector store. + + To use, you should have both: + - the ``oracledb`` python package installed + - a connection string associated with a OracleDBCluster having deployed an + Search index + + Example: + .. code-block:: python + + from langchain_oracledb.vectorstores import OracleVS + from langchain.embeddings.openai import OpenAIEmbeddings + import oracledb + + with oracledb.connect(user = user, password = pwd, dsn = dsn) as + connection: + print ("Database version:", connection.version) + embeddings = OpenAIEmbeddings() + query = "" + vectors = OracleVS(connection, embeddings, table_name, query) + """ + + def __init__( + self, + client: Any, + embedding_function: Union[ + Callable[[str], List[float]], + Embeddings, + ], + table_name: str, + distance_strategy: DistanceStrategy = DistanceStrategy.EUCLIDEAN_DISTANCE, + query: Optional[str] = "What is a Oracle database", + params: Optional[Dict[str, Any]] = None, + ): + self.insert_mode = "array" + connection = _get_connection(client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + + if hasattr(connection, "thin") and connection.thin: + if oracledb.__version__ == "2.1.0": + raise Exception( + "Oracle DB python thin client driver version 2.1.0 not supported" + ) + elif _compare_version(oracledb.__version__, "2.2.0"): + self.insert_mode = "clob" + else: + self.insert_mode = "array" + else: + if (_compare_version(oracledb.__version__, "2.1.0")) and ( + not ( + _compare_version( + ".".join(map(str, oracledb.clientversion())), "23.4" + ) + ) + ): + raise Exception( + "Oracle DB python thick client driver version earlier than " + "2.1.0 not supported with client libraries greater than " + "equal to 23.4" + ) + + if _compare_version(".".join(map(str, oracledb.clientversion())), "23.4"): + self.insert_mode = "clob" + else: + self.insert_mode = "array" + + if _compare_version(oracledb.__version__, "2.1.0"): + self.insert_mode = "clob" + + try: + """Initialize with oracledb client.""" + self.client = client + """Initialize with necessary components.""" + if not isinstance(embedding_function, Embeddings): + logger.warning( + "`embedding_function` is expected to be an Embeddings " + "object, support " + "for passing in a function will soon be removed." + ) + self.embedding_function = embedding_function + self.query = query + embedding_dim = self.get_embedding_dimension() + + self.table_name = table_name + self.distance_strategy = distance_strategy + self.params = params + _create_table(connection, table_name, embedding_dim) + except oracledb.DatabaseError as db_err: + logger.exception(f"Database error occurred while create table: {db_err}") + raise RuntimeError( + "Failed to create table due to a database error." + ) from db_err + except ValueError as val_err: + logger.exception(f"Validation error: {val_err}") + raise RuntimeError( + "Failed to create table due to a validation error." + ) from val_err + except Exception as ex: + logger.exception("An unexpected error occurred while creating the index.") + raise RuntimeError( + "Failed to create table due to an unexpected error." + ) from ex + + @property + def embeddings(self) -> Optional[Embeddings]: + """ + A property that returns an Embeddings instance embedding_function + is an instance of Embeddings, otherwise returns None. + + Returns: + Optional[Embeddings]: The embedding function if it's an instance of + Embeddings, otherwise None. + """ + return ( + self.embedding_function + if isinstance(self.embedding_function, Embeddings) + else None + ) + + def get_embedding_dimension(self) -> int: + # Embed the single document by wrapping it in a list + embedded_document = self._embed_documents( + [self.query if self.query is not None else ""] + ) + + # Get the first (and only) embedding's dimension + return len(embedded_document[0]) + + def _embed_documents(self, texts: List[str]) -> List[List[float]]: + if isinstance(self.embedding_function, Embeddings): + return self.embedding_function.embed_documents(texts) + elif callable(self.embedding_function): + return [self.embedding_function(text) for text in texts] + else: + raise TypeError( + "The embedding_function is neither Embeddings nor callable." + ) + + def _embed_query(self, text: str) -> List[float]: + if isinstance(self.embedding_function, Embeddings): + return self.embedding_function.embed_query(text) + else: + return self.embedding_function(text) + + @_handle_exceptions + def add_texts( + self, + texts: Iterable[str], + metadatas: Optional[List[Dict[Any, Any]]] = None, + ids: Optional[List[str]] = None, + **kwargs: Any, + ) -> List[str]: + """Add more texts to the vectorstore index. + Args: + texts: Iterable of strings to add to the vectorstore. + metadatas: Optional list of metadatas associated with the texts. + ids: Optional list of ids for the texts that are being added to + the vector store. + kwargs: vectorstore specific parameters + """ + + texts = list(texts) + if ids: + # If ids are provided, hash them to maintain consistency + processed_ids = [ + hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids + ] + elif metadatas and all("id" in metadata for metadata in metadatas): + # If no ids are provided but metadatas with ids are, generate + # ids from metadatas + processed_ids = [ + hashlib.sha256(metadata["id"].encode()).hexdigest()[:16].upper() + for metadata in metadatas + ] + else: + # Generate new ids if none are provided + generated_ids = [ + str(uuid.uuid4()) for _ in texts + ] # uuid4 is more standard for random UUIDs + processed_ids = [ + hashlib.sha256(_id.encode()).hexdigest()[:16].upper() + for _id in generated_ids + ] + + embeddings = self._embed_documents(texts) + if not metadatas: + metadatas = [{} for _ in texts] + + docs: List[Tuple[Any, Any, Any, Any]] + if self.insert_mode == "clob": + docs = [ + (id_, json.dumps(embedding), json.dumps(metadata), text) + for id_, embedding, metadata, text in zip( + processed_ids, embeddings, metadatas, texts + ) + ] + else: + docs = [ + (id_, array.array("f", embedding), json.dumps(metadata), text) + for id_, embedding, metadata, text in zip( + processed_ids, embeddings, metadatas, texts + ) + ] + + connection = _get_connection(self.client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + with connection.cursor() as cursor: + cursor.executemany( + f"INSERT INTO {self.table_name} (id, embedding, metadata, " + f"text) VALUES (:1, :2, :3, :4)", + docs, + ) + connection.commit() + return processed_ids + + def similarity_search( + self, + query: str, + k: int = 4, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + """Return docs most similar to query.""" + embedding: List[float] = [] + if isinstance(self.embedding_function, Embeddings): + embedding = self.embedding_function.embed_query(query) + documents = self.similarity_search_by_vector( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return documents + + def similarity_search_by_vector( + self, + embedding: List[float], + k: int = 4, + filter: Optional[dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + docs_and_scores = self.similarity_search_by_vector_with_relevance_scores( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return [doc for doc, _ in docs_and_scores] + + def similarity_search_with_score( + self, + query: str, + k: int = 4, + filter: Optional[dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + """Return docs most similar to query.""" + embedding: List[float] = [] + if isinstance(self.embedding_function, Embeddings): + embedding = self.embedding_function.embed_query(query) + docs_and_scores = self.similarity_search_by_vector_with_relevance_scores( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return docs_and_scores + + @_handle_exceptions + def _get_clob_value(self, result: Any) -> str: + clob_value = "" + if result: + if isinstance(result, oracledb.LOB): + raw_data = result.read() + if isinstance(raw_data, bytes): + clob_value = raw_data.decode( + "utf-8" + ) # Specify the correct encoding + else: + clob_value = raw_data + elif isinstance(result, str): + clob_value = result + else: + raise Exception("Unexpected type:", type(result)) + return clob_value + + @_handle_exceptions + def similarity_search_by_vector_with_relevance_scores( + self, + embedding: List[float], + k: int = 4, + filter: Optional[dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + docs_and_scores = [] + + embedding_arr: Any + if self.insert_mode == "clob": + embedding_arr = json.dumps(embedding) + else: + embedding_arr = array.array("f", embedding) + + query = f""" + SELECT id, + text, + metadata, + vector_distance(embedding, :embedding, + {_get_distance_function(self.distance_strategy)}) as distance + FROM {self.table_name} + ORDER BY distance + FETCH APPROX FIRST {k} ROWS ONLY + """ + + # Execute the query + connection = _get_connection(self.client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + with connection.cursor() as cursor: + cursor.execute(query, embedding=embedding_arr) + results = cursor.fetchall() + + # Filter results if filter is provided + for result in results: + metadata = dict(result[2]) if isinstance(result[2], dict) else {} + + # Apply filtering based on the 'filter' dictionary + if filter: + if all(metadata.get(key) in value for key, value in filter.items()): + doc = Document( + page_content=( + self._get_clob_value(result[1]) + if result[1] is not None + else "" + ), + metadata=metadata, + ) + distance = result[3] + docs_and_scores.append((doc, distance)) + else: + doc = Document( + page_content=( + self._get_clob_value(result[1]) + if result[1] is not None + else "" + ), + metadata=metadata, + ) + distance = result[3] + docs_and_scores.append((doc, distance)) + + return docs_and_scores + + @_handle_exceptions + def similarity_search_by_vector_returning_embeddings( + self, + embedding: List[float], + k: int, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float, NDArray[np.float32]]]: + embedding_arr: Any + if self.insert_mode == "clob": + embedding_arr = json.dumps(embedding) + else: + embedding_arr = array.array("f", embedding) + + documents = [] + + query = f""" + SELECT id, + text, + metadata, + vector_distance(embedding, :embedding, { + _get_distance_function(self.distance_strategy) + }) as distance, + embedding + FROM {self.table_name} + ORDER BY distance + FETCH APPROX FIRST {k} ROWS ONLY + """ + + # Execute the query + connection = _get_connection(self.client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + with connection.cursor() as cursor: + cursor.execute(query, embedding=embedding_arr) + results = cursor.fetchall() + + for result in results: + page_content_str = self._get_clob_value(result[1]) + metadata = result[2] if isinstance(result[2], dict) else {} + + # Apply filter if provided and matches; otherwise, add all + # documents + if not filter or all( + metadata.get(key) in value for key, value in filter.items() + ): + document = Document( + page_content=page_content_str, metadata=metadata + ) + distance = result[3] + + # Assuming result[4] is already in the correct format; + # adjust if necessary + current_embedding = ( + np.array(result[4], dtype=np.float32) + if result[4] + else np.empty(0, dtype=np.float32) + ) + + documents.append((document, distance, current_embedding)) + return documents + + @_handle_exceptions + def max_marginal_relevance_search_with_score_by_vector( + self, + embedding: List[float], + *, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + filter: Optional[Dict[str, Any]] = None, + ) -> List[Tuple[Document, float]]: + """Return docs and their similarity scores selected using the + maximal marginal + relevance. + + Maximal marginal relevance optimizes for similarity to query AND + diversity + among selected documents. + + Args: + self: An instance of the class + embedding: Embedding to look up documents similar to. + k: Number of Documents to return. Defaults to 4. + fetch_k: Number of Documents to fetch before filtering to + pass to MMR algorithm. + filter: (Optional[Dict[str, str]]): Filter by metadata. Defaults + to None. + lambda_mult: Number between 0 and 1 that determines the degree + of diversity among the results with 0 corresponding + to maximum diversity and 1 to minimum diversity. + Defaults to 0.5. + Returns: + List of Documents and similarity scores selected by maximal + marginal + relevance and score for each. + """ + + # Fetch documents and their scores + docs_scores_embeddings = self.similarity_search_by_vector_returning_embeddings( + embedding, fetch_k, filter=filter + ) + # Assuming documents_with_scores is a list of tuples (Document, score) + + # If you need to split documents and scores for processing (e.g., + # for MMR calculation) + documents, scores, embeddings = ( + zip(*docs_scores_embeddings) if docs_scores_embeddings else ([], [], []) + ) + + # Assume maximal_marginal_relevance method accepts embeddings and + # scores, and returns indices of selected docs + mmr_selected_indices = maximal_marginal_relevance( + np.array(embedding, dtype=np.float32), + list(embeddings), + k=k, + lambda_mult=lambda_mult, + ) + + # Filter documents based on MMR-selected indices and map scores + mmr_selected_documents_with_scores = [ + (documents[i], scores[i]) for i in mmr_selected_indices + ] + + return mmr_selected_documents_with_scores + + @_handle_exceptions + def max_marginal_relevance_search_by_vector( + self, + embedding: List[float], + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + """Return docs selected using the maximal marginal relevance. + + Maximal marginal relevance optimizes for similarity to query AND + diversity + among selected documents. + + Args: + self: An instance of the class + embedding: Embedding to look up documents similar to. + k: Number of Documents to return. Defaults to 4. + fetch_k: Number of Documents to fetch to pass to MMR algorithm. + lambda_mult: Number between 0 and 1 that determines the degree + of diversity among the results with 0 corresponding + to maximum diversity and 1 to minimum diversity. + Defaults to 0.5. + filter: Optional[Dict[str, Any]] + **kwargs: Any + Returns: + List of Documents selected by maximal marginal relevance. + """ + docs_and_scores = self.max_marginal_relevance_search_with_score_by_vector( + embedding, k=k, fetch_k=fetch_k, lambda_mult=lambda_mult, filter=filter + ) + return [doc for doc, _ in docs_and_scores] + + @_handle_exceptions + def max_marginal_relevance_search( + self, + query: str, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + """Return docs selected using the maximal marginal relevance. + + Maximal marginal relevance optimizes for similarity to query AND + diversity + among selected documents. + + Args: + self: An instance of the class + query: Text to look up documents similar to. + k: Number of Documents to return. Defaults to 4. + fetch_k: Number of Documents to fetch to pass to MMR algorithm. + lambda_mult: Number between 0 and 1 that determines the degree + of diversity among the results with 0 corresponding + to maximum diversity and 1 to minimum diversity. + Defaults to 0.5. + filter: Optional[Dict[str, Any]] + **kwargs + Returns: + List of Documents selected by maximal marginal relevance. + + `max_marginal_relevance_search` requires that `query` returns matched + embeddings alongside the match documents. + """ + embedding = self._embed_query(query) + documents = self.max_marginal_relevance_search_by_vector( + embedding, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + filter=filter, + **kwargs, + ) + return documents + + @_handle_exceptions + def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: + """Delete by vector IDs. + Args: + self: An instance of the class + ids: List of ids to delete. + **kwargs + """ + + if ids is None: + raise ValueError("No ids provided to delete.") + + # Compute SHA-256 hashes of the ids and truncate them + hashed_ids = [ + hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids + ] + + # Constructing the SQL statement with individual placeholders + placeholders = ", ".join([":id" + str(i + 1) for i in range(len(hashed_ids))]) + + ddl = f"DELETE FROM {self.table_name} WHERE id IN ({placeholders})" + + # Preparing bind variables + bind_vars = { + f"id{i}": hashed_id for i, hashed_id in enumerate(hashed_ids, start=1) + } + + connection = _get_connection(self.client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + with connection.cursor() as cursor: + cursor.execute(ddl, bind_vars) + connection.commit() + + @classmethod + @_handle_exceptions + def from_texts( + cls: Type[OracleVS], + texts: Iterable[str], + embedding: Embeddings, + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> OracleVS: + client: Any = kwargs.get("client", None) + if client is None: + raise ValueError("client parameter is required...") + + params = kwargs.get("params", {}) + + table_name = str(kwargs.get("table_name", "langchain")) + + distance_strategy = cast( + DistanceStrategy, kwargs.get("distance_strategy", None) + ) + if not isinstance(distance_strategy, DistanceStrategy): + raise TypeError( + f"Expected DistanceStrategy got {type(distance_strategy).__name__} " + ) + + query = kwargs.get("query", "What is a Oracle database") + + drop_table_purge(client, table_name) + + vss = cls( + client=client, + embedding_function=embedding, + table_name=table_name, + distance_strategy=distance_strategy, + query=query, + params=params, + ) + vss.add_texts(texts=list(texts), metadatas=metadatas) + return vss diff --git a/libs/oracledb/poetry.lock b/libs/oracledb/poetry.lock new file mode 100644 index 0000000..11ce62c --- /dev/null +++ b/libs/oracledb/poetry.lock @@ -0,0 +1,3898 @@ +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.12.13" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6"}, + {file = "aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad"}, + {file = "aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3"}, + {file = "aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd"}, + {file = "aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5"}, + {file = "aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf"}, + {file = "aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3"}, + {file = "aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd"}, + {file = "aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36f6c973e003dc9b0bb4e8492a643641ea8ef0e97ff7aaa5c0f53d68839357b4"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6cbfc73179bd67c229eb171e2e3745d2afd5c711ccd1e40a68b90427f282eab1"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e8b27b2d414f7e3205aa23bb4a692e935ef877e3a71f40d1884f6e04fd7fa74"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eabded0c2b2ef56243289112c48556c395d70150ce4220d9008e6b4b3dd15690"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:003038e83f1a3ff97409999995ec02fe3008a1d675478949643281141f54751d"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b6f46613031dbc92bdcaad9c4c22c7209236ec501f9c0c5f5f0b6a689bf50f3"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c332c6bb04650d59fb94ed96491f43812549a3ba6e7a16a218e612f99f04145e"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fea41a2c931fb582cb15dc86a3037329e7b941df52b487a9f8b5aa960153cbd"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:846104f45d18fb390efd9b422b27d8f3cf8853f1218c537f36e71a385758c896"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d6c85ac7dd350f8da2520bac8205ce99df4435b399fa7f4dc4a70407073e390"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5a1ecce0ed281bec7da8550da052a6b89552db14d0a0a45554156f085a912f48"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5304d74867028cca8f64f1cc1215eb365388033c5a691ea7aa6b0dc47412f495"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:64d1f24ee95a2d1e094a4cd7a9b7d34d08db1bbcb8aa9fb717046b0a884ac294"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:119c79922a7001ca6a9e253228eb39b793ea994fd2eccb79481c64b5f9d2a055"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb18f00396d22e2f10cd8825d671d9f9a3ba968d708a559c02a627536b36d91c"}, + {file = "aiohttp-3.12.13-cp39-cp39-win32.whl", hash = "sha256:0022de47ef63fd06b065d430ac79c6b0bd24cdae7feaf0e8c6bac23b805a23a8"}, + {file = "aiohttp-3.12.13-cp39-cp39-win_amd64.whl", hash = "sha256:29e08111ccf81b2734ae03f1ad1cb03b9615e7d8f616764f22f71209c094f122"}, + {file = "aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiosignal" +version = "1.4.0" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.9.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "certifi" +version = "2025.7.9" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main", "test"] +files = [ + {file = "certifi-2025.7.9-py3-none-any.whl", hash = "sha256:d842783a14f8fdd646895ac26f719a061408834473cfc10203f6a575beb15d39"}, + {file = "certifi-2025.7.9.tar.gz", hash = "sha256:c1d2ec05395148ee10cf672ffc28cd37ea0ab0d99f9cc74c43e588cbd111b079"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main", "test"] +files = [ + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, +] + +[[package]] +name = "codespell" +version = "2.4.1" +description = "Fix common misspellings in text files" +optional = false +python-versions = ">=3.8" +groups = ["codespell"] +files = [ + {file = "codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425"}, + {file = "codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5"}, +] + +[package.extras] +dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] +hard-encoding-detection = ["chardet"] +toml = ["tomli ; python_version < \"3.11\""] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["test"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.9.2" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912"}, + {file = "coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f"}, + {file = "coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f"}, + {file = "coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf"}, + {file = "coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547"}, + {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45"}, + {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2"}, + {file = "coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e"}, + {file = "coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e"}, + {file = "coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c"}, + {file = "coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba"}, + {file = "coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa"}, + {file = "coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a"}, + {file = "coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc"}, + {file = "coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2"}, + {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c"}, + {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd"}, + {file = "coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74"}, + {file = "coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6"}, + {file = "coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7"}, + {file = "coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62"}, + {file = "coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0"}, + {file = "coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3"}, + {file = "coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1"}, + {file = "coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615"}, + {file = "coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b"}, + {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9"}, + {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f"}, + {file = "coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d"}, + {file = "coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355"}, + {file = "coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0"}, + {file = "coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b"}, + {file = "coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038"}, + {file = "coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d"}, + {file = "coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3"}, + {file = "coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14"}, + {file = "coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6"}, + {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b"}, + {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d"}, + {file = "coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868"}, + {file = "coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a"}, + {file = "coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b"}, + {file = "coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694"}, + {file = "coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5"}, + {file = "coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b"}, + {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3"}, + {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8"}, + {file = "coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46"}, + {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584"}, + {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e"}, + {file = "coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac"}, + {file = "coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926"}, + {file = "coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd"}, + {file = "coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb"}, + {file = "coverage-7.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddc39510ac922a5c4c27849b739f875d3e1d9e590d1e7b64c98dadf037a16cce"}, + {file = "coverage-7.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a535c0c7364acd55229749c2b3e5eebf141865de3a8f697076a3291985f02d30"}, + {file = "coverage-7.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df0f9ef28e0f20c767ccdccfc5ae5f83a6f4a2fbdfbcbcc8487a8a78771168c8"}, + {file = "coverage-7.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f3da12e0ccbcb348969221d29441ac714bbddc4d74e13923d3d5a7a0bebef7a"}, + {file = "coverage-7.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a17eaf46f56ae0f870f14a3cbc2e4632fe3771eab7f687eda1ee59b73d09fe4"}, + {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:669135a9d25df55d1ed56a11bf555f37c922cf08d80799d4f65d77d7d6123fcf"}, + {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9d3a700304d01a627df9db4322dc082a0ce1e8fc74ac238e2af39ced4c083193"}, + {file = "coverage-7.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:71ae8b53855644a0b1579d4041304ddc9995c7b21c8a1f16753c4d8903b4dfed"}, + {file = "coverage-7.9.2-cp39-cp39-win32.whl", hash = "sha256:dd7a57b33b5cf27acb491e890720af45db05589a80c1ffc798462a765be6d4d7"}, + {file = "coverage-7.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f65bb452e579d5540c8b37ec105dd54d8b9307b07bcaa186818c104ffda22441"}, + {file = "coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050"}, + {file = "coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4"}, + {file = "coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "cryptography" +version = "43.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.13\"" +files = [ + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "cryptography" +version = "45.0.5" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9"}, + {file = "cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27"}, + {file = "cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e"}, + {file = "cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174"}, + {file = "cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9"}, + {file = "cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63"}, + {file = "cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42"}, + {file = "cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492"}, + {file = "cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0"}, + {file = "cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a"}, + {file = "cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f"}, + {file = "cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97"}, + {file = "cryptography-45.0.5-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd"}, + {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097"}, + {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e"}, + {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30"}, + {file = "cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e"}, + {file = "cryptography-45.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d"}, + {file = "cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e"}, + {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6"}, + {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18"}, + {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463"}, + {file = "cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1"}, + {file = "cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f"}, + {file = "cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a"}, +] + +[package.dependencies] +cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""] +pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==45.0.5)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = "<4.0,>=3.7" +groups = ["main"] +files = [ + {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, + {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main", "test"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] + +[[package]] +name = "frozenlist" +version = "1.7.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, + {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, +] + +[[package]] +name = "fsspec" +version = "2025.5.1" +description = "File-system specification" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462"}, + {file = "fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475"}, +] + +[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[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 = "greenlet" +version = "3.2.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.9" +groups = ["main"] +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\")" +files = [ + {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"}, + {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"}, + {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"}, + {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"}, + {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"}, + {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"}, + {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"}, + {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"}, + {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"}, + {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"}, + {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"}, + {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"}, + {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"}, + {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "hf-xet" +version = "1.1.5" +description = "Fast transfer of large files with the Hugging Face Hub." +optional = false +python-versions = ">=3.8" +groups = ["test"] +markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" +files = [ + {file = "hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23"}, + {file = "hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8"}, + {file = "hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1"}, + {file = "hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18"}, + {file = "hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14"}, + {file = "hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a"}, + {file = "hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245"}, + {file = "hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "httpcore" +version = "1.0.9" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.16" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "httpx-sse" +version = "0.4.1" +description = "Consume Server-Sent Event (SSE) messages with HTTPX." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37"}, + {file = "httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e"}, +] + +[[package]] +name = "huggingface-hub" +version = "0.33.2" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +groups = ["test"] +files = [ + {file = "huggingface_hub-0.33.2-py3-none-any.whl", hash = "sha256:3749498bfa91e8cde2ddc2c1db92c79981f40e66434c20133b39e5928ac9bcc5"}, + {file = "huggingface_hub-0.33.2.tar.gz", hash = "sha256:84221defaec8fa09c090390cd68c78b88e3c4c2b7befba68d3dc5aacbc3c2c5f"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +hf-xet = {version = ">=1.1.2,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} +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", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (==1.4.0)", "mypy (==1.15.0) ; python_version >= \"3.9\"", "mypy (>=1.14.1,<1.15.0) ; python_version == \"3.8\"", "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", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (==1.4.0)", "mypy (==1.15.0) ; python_version >= \"3.9\"", "mypy (>=1.14.1,<1.15.0) ; python_version == \"3.8\"", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +hf-transfer = ["hf-transfer (>=0.1.4)"] +hf-xet = ["hf-xet (>=1.1.2,<2.0.0)"] +inference = ["aiohttp"] +mcp = ["aiohttp", "mcp (>=1.8.0)", "typer"] +oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] +quality = ["libcst (==1.4.0)", "mypy (==1.15.0) ; python_version >= \"3.9\"", "mypy (>=1.14.1,<1.15.0) ; python_version == \"3.8\"", "ruff (>=0.9.0)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +tensorflow-testing = ["keras (<3.0)", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "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" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main", "test"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "joblib" +version = "1.5.1" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a"}, + {file = "joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444"}, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main"] +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "langchain" +version = "0.3.26" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "langchain-0.3.26-py3-none-any.whl", hash = "sha256:361bb2e61371024a8c473da9f9c55f4ee50f269c5ab43afdb2b1309cb7ac36cf"}, + {file = "langchain-0.3.26.tar.gz", hash = "sha256:8ff034ee0556d3e45eff1f1e96d0d745ced57858414dba7171c8ebdbeb5580c9"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} +langchain-core = ">=0.3.66,<1.0.0" +langchain-text-splitters = ">=0.3.8,<1.0.0" +langsmith = ">=0.1.17" +pydantic = ">=2.7.4,<3.0.0" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" + +[package.extras] +anthropic = ["langchain-anthropic"] +aws = ["langchain-aws"] +azure-ai = ["langchain-azure-ai"] +cohere = ["langchain-cohere"] +community = ["langchain-community"] +deepseek = ["langchain-deepseek"] +fireworks = ["langchain-fireworks"] +google-genai = ["langchain-google-genai"] +google-vertexai = ["langchain-google-vertexai"] +groq = ["langchain-groq"] +huggingface = ["langchain-huggingface"] +mistralai = ["langchain-mistralai"] +ollama = ["langchain-ollama"] +openai = ["langchain-openai"] +perplexity = ["langchain-perplexity"] +together = ["langchain-together"] +xai = ["langchain-xai"] + +[[package]] +name = "langchain-community" +version = "0.3.27" +description = "Community contributed LangChain integrations." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "langchain_community-0.3.27-py3-none-any.whl", hash = "sha256:581f97b795f9633da738ea95da9cb78f8879b538090c9b7a68c0aed49c828f0d"}, + {file = "langchain_community-0.3.27.tar.gz", hash = "sha256:e1037c3b9da0c6d10bf06e838b034eb741e016515c79ef8f3f16e53ead33d882"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +dataclasses-json = ">=0.5.7,<0.7" +httpx-sse = ">=0.4.0,<1.0.0" +langchain = ">=0.3.26,<1.0.0" +langchain-core = ">=0.3.66,<1.0.0" +langsmith = ">=0.1.125" +numpy = [ + {version = ">=1.26.2", markers = "python_version < \"3.13\""}, + {version = ">=2.1.0", markers = "python_version >= \"3.13\""}, +] +pydantic-settings = ">=2.4.0,<3.0.0" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" + +[[package]] +name = "langchain-core" +version = "0.3.68" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "langchain_core-0.3.68-py3-none-any.whl", hash = "sha256:5e5c1fbef419590537c91b8c2d86af896fbcbaf0d5ed7fdcdd77f7d8f3467ba0"}, + {file = "langchain_core-0.3.68.tar.gz", hash = "sha256:312e1932ac9aa2eaf111b70fdc171776fa571d1a86c1f873dcac88a094b19c6f"}, +] + +[package.dependencies] +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.3.45" +packaging = ">=23.2,<25" +pydantic = ">=2.7.4" +PyYAML = ">=5.3" +tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" +typing-extensions = ">=4.7" + +[[package]] +name = "langchain-text-splitters" +version = "0.3.8" +description = "LangChain text splitting utilities" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "langchain_text_splitters-0.3.8-py3-none-any.whl", hash = "sha256:e75cc0f4ae58dcf07d9f18776400cf8ade27fadd4ff6d264df6278bb302f6f02"}, + {file = "langchain_text_splitters-0.3.8.tar.gz", hash = "sha256:116d4b9f2a22dda357d0b79e30acf005c5518177971c66a9f1ab0edfdb0f912e"}, +] + +[package.dependencies] +langchain-core = ">=0.3.51,<1.0.0" + +[[package]] +name = "langsmith" +version = "0.4.4" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "langsmith-0.4.4-py3-none-any.whl", hash = "sha256:014c68329bd085bd6c770a6405c61bb6881f82eb554ce8c4d1984b0035fd1716"}, + {file = "langsmith-0.4.4.tar.gz", hash = "sha256:70c53bbff24a7872e88e6fa0af98270f4986a6e364f9e85db1cc5636defa4d66"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<1" +orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} +packaging = ">=23.2" +pydantic = ">=1,<3" +requests = ">=2,<3" +requests-toolbelt = ">=1.0.0,<2.0.0" +zstandard = ">=0.23.0,<0.24.0" + +[package.extras] +langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] +openai-agents = ["openai-agents (>=0.0.3,<0.1)"] +otel = ["opentelemetry-api (>=1.30.0,<2.0.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0,<2.0.0)", "opentelemetry-sdk (>=1.30.0,<2.0.0)"] +pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, + {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<5.0)", "tox"] +docs = ["autodocsumm (==0.2.14)", "furo (==2024.8.6)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)", "sphinx-issues (==5.0.0)", "sphinxext-opengraph (==0.9.1)"] +tests = ["pytest", "simplejson"] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +groups = ["test"] +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "multidict" +version = "6.6.3" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b"}, + {file = "multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318"}, + {file = "multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485"}, + {file = "multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183"}, + {file = "multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5"}, + {file = "multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2"}, + {file = "multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10"}, + {file = "multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5"}, + {file = "multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17"}, + {file = "multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6"}, + {file = "multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e"}, + {file = "multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9"}, + {file = "multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c"}, + {file = "multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e"}, + {file = "multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d"}, + {file = "multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8161b5a7778d3137ea2ee7ae8a08cce0010de3b00ac671c5ebddeaa17cefd22"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1328201ee930f069961ae707d59c6627ac92e351ed5b92397cf534d1336ce557"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b1db4d2093d6b235de76932febf9d50766cf49a5692277b2c28a501c9637f616"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53becb01dd8ebd19d1724bebe369cfa87e4e7f29abbbe5c14c98ce4c383e16cd"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41bb9d1d4c303886e2d85bade86e59885112a7f4277af5ad47ab919a2251f306"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:775b464d31dac90f23192af9c291dc9f423101857e33e9ebf0020a10bfcf4144"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d04d01f0a913202205a598246cf77826fe3baa5a63e9f6ccf1ab0601cf56eca0"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d25594d3b38a2e6cabfdcafef339f754ca6e81fbbdb6650ad773ea9775af35ab"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35712f1748d409e0707b165bf49f9f17f9e28ae85470c41615778f8d4f7d9609"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c8082e5814b662de8589d6a06c17e77940d5539080cbab9fe6794b5241b76d9"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:61af8a4b771f1d4d000b3168c12c3120ccf7284502a94aa58c68a81f5afac090"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:448e4a9afccbf297577f2eaa586f07067441e7b63c8362a3540ba5a38dc0f14a"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:233ad16999afc2bbd3e534ad8dbe685ef8ee49a37dbc2cdc9514e57b6d589ced"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bb933c891cd4da6bdcc9733d048e994e22e1883287ff7540c2a0f3b117605092"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37b09ca60998e87734699e88c2363abfd457ed18cfbf88e4009a4e83788e63ed"}, + {file = "multidict-6.6.3-cp39-cp39-win32.whl", hash = "sha256:f54cb79d26d0cd420637d184af38f0668558f3c4bbe22ab7ad830e67249f2e0b"}, + {file = "multidict-6.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:295adc9c0551e5d5214b45cf29ca23dbc28c2d197a9c30d51aed9e037cb7c578"}, + {file = "multidict-6.6.3-cp39-cp39-win_arm64.whl", hash = "sha256:15332783596f227db50fb261c2c251a58ac3873c457f3a550a95d5c0aa3c770d"}, + {file = "multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a"}, + {file = "multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.16.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["typing"] +files = [ + {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, + {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, + {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, + {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, + {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, + {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, + {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, + {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, + {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, + {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, + {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, + {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, + {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["main", "typing"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "networkx" +version = "3.2.1" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "python_version < \"3.13\"" +files = [ + {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, + {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, +] + +[package.extras] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "networkx" +version = "3.5" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.11" +groups = ["test"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}, + {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}, +] + +[package.extras] +default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"] +developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "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)", "pytest-xdist (>=3.0)"] +test-extras = ["pytest-mpl", "pytest-randomly"] + +[[package]] +name = "numpy" +version = "2.0.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["main", "test"] +markers = "python_version < \"3.13\"" +files = [ + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, +] + +[[package]] +name = "numpy" +version = "2.3.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main", "test"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"}, + {file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"}, + {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"}, + {file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"}, + {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"}, + {file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"}, + {file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"}, + {file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"}, + {file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"}, + {file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"}, + {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"}, + {file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"}, + {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"}, + {file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"}, + {file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"}, + {file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"}, + {file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"}, + {file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"}, + {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"}, + {file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"}, + {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"}, + {file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"}, + {file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"}, + {file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"}, + {file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"}, + {file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"}, + {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"}, + {file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"}, + {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"}, + {file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"}, + {file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"}, + {file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"}, + {file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"}, + {file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"}, + {file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"}, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.6.4.1" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb"}, + {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:235f728d6e2a409eddf1df58d5b0921cf80cfa9e72b9f2775ccb7b4a87984668"}, + {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-win_amd64.whl", hash = "sha256:9e4fa264f4d8a4eb0cdbd34beadc029f453b3bafae02401e999cf3d5a5af75f8"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.6.80" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:166ee35a3ff1587f2490364f90eeeb8da06cd867bd5b701bf7f9a02b78bc63fc"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.whl", hash = "sha256:358b4a1d35370353d52e12f0a7d1769fc01ff74a191689d3870b2123156184c4"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-win_amd64.whl", hash = "sha256:bbe6ae76e83ce5251b56e8c8e61a964f757175682bbad058b170b136266ab00a"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.6.77" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5847f1d6e5b757f1d2b3991a01082a44aad6f10ab3c5c0213fa3e25bddc25a13"}, + {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53"}, + {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:f7007dbd914c56bd80ea31bc43e8e149da38f68158f423ba845fc3292684e45a"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.6.77" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6116fad3e049e04791c0256a9778c16237837c08b27ed8c8401e2e45de8d60cd"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d461264ecb429c84c8879a7153499ddc7b19b5f8d84c204307491989a365588e"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:86c58044c824bf3c173c49a2dbc7a6c8b53cb4e4dca50068be0bf64e9dab3f7f"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.5.1.17" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9fd4584468533c61873e5fda8ca41bac3a38bcb2d12350830c69b0a96a7e4def"}, + {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2"}, + {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-win_amd64.whl", hash = "sha256:d7af0f8a4f3b4b9dbb3122f2ef553b45694ed9c384d5a75bab197b8eefb79ab8"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.0.4" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d16079550df460376455cba121db6564089176d9bac9e4f360493ca4741b22a6"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8510990de9f96c803a051822618d42bf6cb8f069ff3f48d93a8486efdacb48fb"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-win_amd64.whl", hash = "sha256:6048ebddfb90d09d2707efb1fd78d4e3a77cb3ae4dc60e19aab6be0ece2ae464"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.11.1.6" +description = "cuFile GPUDirect libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159"}, + {file = "nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:8f57a0051dcf2543f6dc2b98a98cb2719c37d3cee1baba8965d57f3bbc90d4db"}, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.7.77" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6e82df077060ea28e37f48a3ec442a8f47690c7499bff392a5938614b56c98d8"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:7b2ed8e95595c3591d984ea3603dd66fe6ce6812b886d59049988a712ed06b6e"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-win_amd64.whl", hash = "sha256:6d6d935ffba0f3d439b7cd968192ff068fafd9018dbf1b85b37261b13cfc9905"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.1.2" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0ce237ef60acde1efc457335a2ddadfd7610b892d94efee7b776c64bb1cac9e0"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dbbe4fc38ec1289c7e5230e16248365e375c3673c9c8bac5796e2e20db07f56e"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-win_amd64.whl", hash = "sha256:6813f9d8073f555444a8705f3ab0296d3e1cb37a16d694c5fc8b862a0d8706d7"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.4.2" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d25b62fb18751758fe3c93a4a08eff08effedfe4edf1c6bb5afd0890fe88f887"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7aa32fa5470cf754f72d1116c7cbc300b4e638d3ae5304cfa4a638a5b87161b1"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-win_amd64.whl", hash = "sha256:4acb8c08855a26d737398cba8fb6f8f5045d93f82612b4cfd84645a2332ccf20"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.3" +description = "NVIDIA cuSPARSELt" +optional = false +python-versions = "*" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8371549623ba601a06322af2133c4a44350575f5a3108fb75f3ef20b822ad5f1"}, + {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46"}, + {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-win_amd64.whl", hash = "sha256:3b325bcbd9b754ba43df5a311488fca11a6b5dc3d11df4d190c000cf1a0765c7"}, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.26.2" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c196e95e832ad30fbbb50381eb3cbd1fadd5675e587a548563993609af19522"}, + {file = "nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf4eaa7d4b6b543ffd69d6abfb11efdeb2db48270d94dfd3a452c24150829e41"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-win_amd64.whl", hash = "sha256:e61120e52ed675747825cdd16febc6a0730537451d867ee58bee3853b1b13d1c"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.6.77" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f44f8d86bb7d5629988d61c8d3ae61dddb2015dee142740536bc7481b022fe4b"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:adcaabb9d436c9761fca2b13959a2d237c5f9fd406c8e4b723c695409ff88059"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:2fb11a4af04a5e6c84073e6404d26588a34afd35379f0855a99797897efa75c0"}, +] + +[[package]] +name = "oracledb" +version = "3.2.0" +description = "Python interface to Oracle Database" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "oracledb-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f69ecfe4322babac464b26d23e3f2ac131839facbe6ea15fc32f6a7df91c6fc7"}, + {file = "oracledb-3.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de666090d92a9b50744c6d765966ff9aec1b8e2e48eccbc451d2a6107dd555c3"}, + {file = "oracledb-3.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:605cb174b19406f9f91c8e1ae2834c56be678113df25980f378bda60a48b7411"}, + {file = "oracledb-3.2.0-cp310-cp310-win32.whl", hash = "sha256:ccfd8ebc8d389795ba8459feb467d9c4cf747ab6b005d4abf2fe8a1c24d5504d"}, + {file = "oracledb-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d3666a2d8bc69a19dfbaa9618b63602d879f57bad7e61f7fae4c45caf185a6b"}, + {file = "oracledb-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:42e936190c5c76b115e2803e9c51a416a895c31ca551bc154b8c52121a84ee78"}, + {file = "oracledb-3.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a87a331df19a84cad3d50a99cd505f8d0ec500e5e22438a025bd66e037b20715"}, + {file = "oracledb-3.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b62f0c018e437b91254f019715a5fd5daafcf3182afee86ccd9d0f3dac75c0a7"}, + {file = "oracledb-3.2.0-cp311-cp311-win32.whl", hash = "sha256:01477e65f129f8927ef64cd4b48d68646fc0115e1e95484dac5b82dd9da7b98b"}, + {file = "oracledb-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1dc529f06a95ca3560d233609c266c17e828a08a70b8a434e2c3fe7000825eb"}, + {file = "oracledb-3.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c2658983520b460776e74c75bb50e51a78e8ab743b64adc844a26a3a8a0bc7c"}, + {file = "oracledb-3.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c7a599ffe3238824951948992ab6b4532a0c1d4b33900d412f738a7da476d47"}, + {file = "oracledb-3.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9388ad0e09dc4030cd62779acc0ae4e9dfd338da7d30c72768fb0589461485ff"}, + {file = "oracledb-3.2.0-cp312-cp312-win32.whl", hash = "sha256:94ac95e52e6f4a9394408aba6cc5f90581219ecb872d450b2a80df9aa3cc4216"}, + {file = "oracledb-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a367604d091ee82c9e3f6d97e34b8ec919646209bda78337f6156b76252dd024"}, + {file = "oracledb-3.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3a93e2d4a6f6f75a7f18ce48d7fd8862343609667bab5c286ac89651007f628b"}, + {file = "oracledb-3.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1499b86c2ed05be26c3e88c313471828639a49ebf718ed2d3bc69cec1caa299d"}, + {file = "oracledb-3.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90c31c9acc6ba8f5b539317c7190851c8db905d5d5bafd020a8f4f1f18930b1c"}, + {file = "oracledb-3.2.0-cp313-cp313-win32.whl", hash = "sha256:281b060579699b0ed6d52fafe71a13a28c5bd84bb403d4637a8f8b0c223399c2"}, + {file = "oracledb-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b6d41334c6747ccae3626601ea9118ee1ff2df07602e221539fd55367671fce5"}, + {file = "oracledb-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:003485f2f812685aabf1250a2769869ea7e07a5b321623ce9beca6b903d68cad"}, + {file = "oracledb-3.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e62f10be2da56efe234754dc0c95186e84c9468c74360064e392d0e651239cf"}, + {file = "oracledb-3.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a034d738004e18068c6f2edf5742535cd30afaa8cf4e867b2551dc96790620c6"}, + {file = "oracledb-3.2.0-cp39-cp39-win32.whl", hash = "sha256:56ef7b676d9f7779ad3c3a57d3892f92dc453597a7a360923a1f620287d2e663"}, + {file = "oracledb-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0f55ff1582fe1661a1fa7b128a6cf9e350cbb929e12146e94513e86c1b16178b"}, + {file = "oracledb-3.2.0.tar.gz", hash = "sha256:9bf9f1c93e53142b33d1c5ebf5ababeebd2062a01d5ead68bbb640439ecf2223"}, +] + +[package.dependencies] +cryptography = ">=3.2.1" + +[[package]] +name = "orjson" +version = "3.10.18" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402"}, + {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c"}, + {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9b0aa09745e2c9b3bf779b096fa71d1cc2d801a604ef6dd79c8b1bfef52b2f92"}, + {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53a245c104d2792e65c8d225158f2b8262749ffe64bc7755b00024757d957a13"}, + {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9495ab2611b7f8a0a8a505bcb0f0cbdb5469caafe17b0e404c3c746f9900469"}, + {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73be1cbcebadeabdbc468f82b087df435843c809cd079a565fb16f0f3b23238f"}, + {file = "orjson-3.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8936ee2679e38903df158037a2f1c108129dee218975122e37847fb1d4ac68"}, + {file = "orjson-3.10.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7115fcbc8525c74e4c2b608129bef740198e9a120ae46184dac7683191042056"}, + {file = "orjson-3.10.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:771474ad34c66bc4d1c01f645f150048030694ea5b2709b87d3bda273ffe505d"}, + {file = "orjson-3.10.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c14047dbbea52886dd87169f21939af5d55143dad22d10db6a7514f058156a8"}, + {file = "orjson-3.10.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641481b73baec8db14fdf58f8967e52dc8bda1f2aba3aa5f5c1b07ed6df50b7f"}, + {file = "orjson-3.10.18-cp310-cp310-win32.whl", hash = "sha256:607eb3ae0909d47280c1fc657c4284c34b785bae371d007595633f4b1a2bbe06"}, + {file = "orjson-3.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:8770432524ce0eca50b7efc2a9a5f486ee0113a5fbb4231526d414e6254eba92"}, + {file = "orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8"}, + {file = "orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d"}, + {file = "orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7"}, + {file = "orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a"}, + {file = "orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679"}, + {file = "orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947"}, + {file = "orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4"}, + {file = "orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334"}, + {file = "orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17"}, + {file = "orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e"}, + {file = "orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b"}, + {file = "orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7"}, + {file = "orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1"}, + {file = "orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a"}, + {file = "orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5"}, + {file = "orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753"}, + {file = "orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17"}, + {file = "orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d"}, + {file = "orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae"}, + {file = "orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f"}, + {file = "orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c"}, + {file = "orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad"}, + {file = "orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c"}, + {file = "orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406"}, + {file = "orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6"}, + {file = "orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06"}, + {file = "orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5"}, + {file = "orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e"}, + {file = "orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc"}, + {file = "orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a"}, + {file = "orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147"}, + {file = "orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c"}, + {file = "orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103"}, + {file = "orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595"}, + {file = "orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc"}, + {file = "orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc"}, + {file = "orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049"}, + {file = "orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58"}, + {file = "orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034"}, + {file = "orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1"}, + {file = "orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012"}, + {file = "orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f"}, + {file = "orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea"}, + {file = "orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52"}, + {file = "orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3"}, + {file = "orjson-3.10.18-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95fae14225edfd699454e84f61c3dd938df6629a00c6ce15e704f57b58433bb"}, + {file = "orjson-3.10.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5232d85f177f98e0cefabb48b5e7f60cff6f3f0365f9c60631fecd73849b2a82"}, + {file = "orjson-3.10.18-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2783e121cafedf0d85c148c248a20470018b4ffd34494a68e125e7d5857655d1"}, + {file = "orjson-3.10.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e54ee3722caf3db09c91f442441e78f916046aa58d16b93af8a91500b7bbf273"}, + {file = "orjson-3.10.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2daf7e5379b61380808c24f6fc182b7719301739e4271c3ec88f2984a2d61f89"}, + {file = "orjson-3.10.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f39b371af3add20b25338f4b29a8d6e79a8c7ed0e9dd49e008228a065d07781"}, + {file = "orjson-3.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b819ed34c01d88c6bec290e6842966f8e9ff84b7694632e88341363440d4cc0"}, + {file = "orjson-3.10.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2f6c57debaef0b1aa13092822cbd3698a1fb0209a9ea013a969f4efa36bdea57"}, + {file = "orjson-3.10.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:755b6d61ffdb1ffa1e768330190132e21343757c9aa2308c67257cc81a1a6f5a"}, + {file = "orjson-3.10.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce8d0a875a85b4c8579eab5ac535fb4b2a50937267482be402627ca7e7570ee3"}, + {file = "orjson-3.10.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57b5d0673cbd26781bebc2bf86f99dd19bd5a9cb55f71cc4f66419f6b50f3d77"}, + {file = "orjson-3.10.18-cp39-cp39-win32.whl", hash = "sha256:951775d8b49d1d16ca8818b1f20c4965cae9157e7b562a2ae34d3967b8f21c8e"}, + {file = "orjson-3.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:fdd9d68f83f0bc4406610b1ac68bdcded8c5ee58605cc69e643a06f4d075f429"}, + {file = "orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main", "test"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["typing"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pillow" +version = "11.3.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, + {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, + {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, + {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, + {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, + {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, + {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, + {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, + {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, + {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, + {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, + {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, + {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, + {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, + {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, + {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, + {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, + {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, + {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, + {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, + {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, + {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, + {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, + {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, + {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, + {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +typing = ["typing-extensions ; python_version < \"3.10\""] +xmp = ["defusedxml"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "propcache" +version = "0.3.2" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"}, + {file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"}, +] + +[package.dependencies] +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" + +[package.extras] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-watcher" +version = "0.3.5" +description = "Automatically rerun your tests on file modifications" +optional = false +python-versions = ">=3.7.0,<4.0.0" +groups = ["test"] +files = [ + {file = "pytest_watcher-0.3.5-py3-none-any.whl", hash = "sha256:af00ca52c7be22dc34c0fd3d7ffef99057207a73b05dc5161fe3b2fe91f58130"}, + {file = "pytest_watcher-0.3.5.tar.gz", hash = "sha256:8896152460ba2b1a8200c12117c6611008ec96c8b2d811f0a05ab8a82b043ff8"}, +] + +[package.dependencies] +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +watchdog = ">=2.0.0" + +[[package]] +name = "python-dotenv" +version = "1.1.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main", "test"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +groups = ["test"] +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + +[[package]] +name = "requests" +version = "2.32.4" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +groups = ["main", "test"] +files = [ + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "ruff" +version = "0.1.15" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["lint"] +files = [ + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, +] + +[[package]] +name = "safetensors" +version = "0.5.3" +description = "" +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073"}, + {file = "safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a"}, + {file = "safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135"}, + {file = "safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04"}, + {file = "safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace"}, + {file = "safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11"}, + {file = "safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965"}, +] + +[package.extras] +all = ["safetensors[jax]", "safetensors[numpy]", "safetensors[paddlepaddle]", "safetensors[pinned-tf]", "safetensors[quality]", "safetensors[testing]", "safetensors[torch]"] +dev = ["safetensors[all]"] +jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "safetensors[numpy]"] +mlx = ["mlx (>=0.0.9)"] +numpy = ["numpy (>=1.21.6)"] +paddlepaddle = ["paddlepaddle (>=2.4.1)", "safetensors[numpy]"] +pinned-tf = ["safetensors[numpy]", "tensorflow (==2.18.0)"] +quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] +tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] +testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] +torch = ["safetensors[numpy]", "torch (>=1.10)"] + +[[package]] +name = "scikit-learn" +version = "1.6.1" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "python_version < \"3.13\"" +files = [ + {file = "scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e"}, + {file = "scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36"}, + {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5"}, + {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b"}, + {file = "scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002"}, + {file = "scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33"}, + {file = "scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d"}, + {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2"}, + {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8"}, + {file = "scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415"}, + {file = "scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b"}, + {file = "scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2"}, + {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f"}, + {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86"}, + {file = "scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52"}, + {file = "scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322"}, + {file = "scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1"}, + {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348"}, + {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97"}, + {file = "scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f"}, + {file = "scikit_learn-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1"}, + {file = "scikit_learn-1.6.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e"}, + {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107"}, + {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422"}, + {file = "scikit_learn-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b"}, + {file = "scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.5.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scikit-learn" +version = "1.7.0" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.10" +groups = ["test"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "scikit_learn-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9fe7f51435f49d97bd41d724bb3e11eeb939882af9c29c931a8002c357e8cdd5"}, + {file = "scikit_learn-1.7.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0c93294e1e1acbee2d029b1f2a064f26bd928b284938d51d412c22e0c977eb3"}, + {file = "scikit_learn-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf3755f25f145186ad8c403312f74fb90df82a4dfa1af19dc96ef35f57237a94"}, + {file = "scikit_learn-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2726c8787933add436fb66fb63ad18e8ef342dfb39bbbd19dc1e83e8f828a85a"}, + {file = "scikit_learn-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:e2539bb58886a531b6e86a510c0348afaadd25005604ad35966a85c2ec378800"}, + {file = "scikit_learn-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ef09b1615e1ad04dc0d0054ad50634514818a8eb3ee3dee99af3bffc0ef5007"}, + {file = "scikit_learn-1.7.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:7d7240c7b19edf6ed93403f43b0fcb0fe95b53bc0b17821f8fb88edab97085ef"}, + {file = "scikit_learn-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80bd3bd4e95381efc47073a720d4cbab485fc483966f1709f1fd559afac57ab8"}, + {file = "scikit_learn-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dbe48d69aa38ecfc5a6cda6c5df5abef0c0ebdb2468e92437e2053f84abb8bc"}, + {file = "scikit_learn-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:8fa979313b2ffdfa049ed07252dc94038def3ecd49ea2a814db5401c07f1ecfa"}, + {file = "scikit_learn-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c2c7243d34aaede0efca7a5a96d67fddaebb4ad7e14a70991b9abee9dc5c0379"}, + {file = "scikit_learn-1.7.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f39f6a811bf3f15177b66c82cbe0d7b1ebad9f190737dcdef77cfca1ea3c19c"}, + {file = "scikit_learn-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63017a5f9a74963d24aac7590287149a8d0f1a0799bbe7173c0d8ba1523293c0"}, + {file = "scikit_learn-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f8a0b1e73e9a08b7cc498bb2aeab36cdc1f571f8ab2b35c6e5d1c7115d97d"}, + {file = "scikit_learn-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:34cc8d9d010d29fb2b7cbcd5ccc24ffdd80515f65fe9f1e4894ace36b267ce19"}, + {file = "scikit_learn-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5b7974f1f32bc586c90145df51130e02267e4b7e77cab76165c76cf43faca0d9"}, + {file = "scikit_learn-1.7.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:014e07a23fe02e65f9392898143c542a50b6001dbe89cb867e19688e468d049b"}, + {file = "scikit_learn-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e7ced20582d3a5516fb6f405fd1d254e1f5ce712bfef2589f51326af6346e8"}, + {file = "scikit_learn-1.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1babf2511e6ffd695da7a983b4e4d6de45dce39577b26b721610711081850906"}, + {file = "scikit_learn-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:5abd2acff939d5bd4701283f009b01496832d50ddafa83c90125a4e41c33e314"}, + {file = "scikit_learn-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e39d95a929b112047c25b775035c8c234c5ca67e681ce60d12413afb501129f7"}, + {file = "scikit_learn-1.7.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:0521cb460426c56fee7e07f9365b0f45ec8ca7b2d696534ac98bfb85e7ae4775"}, + {file = "scikit_learn-1.7.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:317ca9f83acbde2883bd6bb27116a741bfcb371369706b4f9973cf30e9a03b0d"}, + {file = "scikit_learn-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:126c09740a6f016e815ab985b21e3a0656835414521c81fc1a8da78b679bdb75"}, + {file = "scikit_learn-1.7.0.tar.gz", hash = "sha256:c01e869b15aec88e2cdb73d27f15bdbe03bce8e2fb43afbe77c45d399e73a5a3"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.22.0" +scipy = ">=1.8.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "pandas (>=1.4.0)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.22.0)", "scipy (>=1.8.0)"] +docs = ["Pillow (>=8.4.0)", "matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] +examples = ["matplotlib (>=3.5.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.22.0)", "scipy (>=1.8.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==3.0.1)"] +tests = ["matplotlib (>=3.5.0)", "mypy (>=1.15)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.2.1)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.11.7)", "scikit-image (>=0.19.0)"] + +[[package]] +name = "scipy" +version = "1.13.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "python_version < \"3.13\"" +files = [ + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, +] + +[package.dependencies] +numpy = ">=1.22.4,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "scipy" +version = "1.16.0" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["test"] +markers = "python_version >= \"3.13\"" +files = [ + {file = "scipy-1.16.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:deec06d831b8f6b5fb0b652433be6a09db29e996368ce5911faf673e78d20085"}, + {file = "scipy-1.16.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d30c0fe579bb901c61ab4bb7f3eeb7281f0d4c4a7b52dbf563c89da4fd2949be"}, + {file = "scipy-1.16.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:b2243561b45257f7391d0f49972fca90d46b79b8dbcb9b2cb0f9df928d370ad4"}, + {file = "scipy-1.16.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e6d7dfc148135e9712d87c5f7e4f2ddc1304d1582cb3a7d698bbadedb61c7afd"}, + {file = "scipy-1.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90452f6a9f3fe5a2cf3748e7be14f9cc7d9b124dce19667b54f5b429d680d539"}, + {file = "scipy-1.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a2f0bf2f58031c8701a8b601df41701d2a7be17c7ffac0a4816aeba89c4cdac8"}, + {file = "scipy-1.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c4abb4c11fc0b857474241b812ce69ffa6464b4bd8f4ecb786cf240367a36a7"}, + {file = "scipy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b370f8f6ac6ef99815b0d5c9f02e7ade77b33007d74802efc8316c8db98fd11e"}, + {file = "scipy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:a16ba90847249bedce8aa404a83fb8334b825ec4a8e742ce6012a7a5e639f95c"}, + {file = "scipy-1.16.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7eb6bd33cef4afb9fa5f1fb25df8feeb1e52d94f21a44f1d17805b41b1da3180"}, + {file = "scipy-1.16.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1dbc8fdba23e4d80394ddfab7a56808e3e6489176d559c6c71935b11a2d59db1"}, + {file = "scipy-1.16.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7dcf42c380e1e3737b343dec21095c9a9ad3f9cbe06f9c05830b44b1786c9e90"}, + {file = "scipy-1.16.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26ec28675f4a9d41587266084c626b02899db373717d9312fa96ab17ca1ae94d"}, + {file = "scipy-1.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:952358b7e58bd3197cfbd2f2f2ba829f258404bdf5db59514b515a8fe7a36c52"}, + {file = "scipy-1.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03931b4e870c6fef5b5c0970d52c9f6ddd8c8d3e934a98f09308377eba6f3824"}, + {file = "scipy-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:512c4f4f85912767c351a0306824ccca6fd91307a9f4318efe8fdbd9d30562ef"}, + {file = "scipy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e69f798847e9add03d512eaf5081a9a5c9a98757d12e52e6186ed9681247a1ac"}, + {file = "scipy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:adf9b1999323ba335adc5d1dc7add4781cb5a4b0ef1e98b79768c05c796c4e49"}, + {file = "scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451"}, + {file = "scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e"}, + {file = "scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974"}, + {file = "scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc"}, + {file = "scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351"}, + {file = "scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32"}, + {file = "scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b"}, + {file = "scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358"}, + {file = "scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe"}, + {file = "scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47"}, + {file = "scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08"}, + {file = "scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176"}, + {file = "scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed"}, + {file = "scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938"}, + {file = "scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1"}, + {file = "scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706"}, + {file = "scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e"}, + {file = "scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db"}, + {file = "scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62"}, +] + +[package.dependencies] +numpy = ">=1.25.2,<2.6" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "sentence-transformers" +version = "5.0.0" +description = "Embeddings, Retrieval, and Reranking" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "sentence_transformers-5.0.0-py3-none-any.whl", hash = "sha256:346240f9cc6b01af387393f03e103998190dfb0826a399d0c38a81a05c7a5d76"}, + {file = "sentence_transformers-5.0.0.tar.gz", hash = "sha256:e5a411845910275fd166bacb01d28b7f79537d3550628ae42309dbdd3d5670d1"}, +] + +[package.dependencies] +huggingface-hub = ">=0.20.0" +Pillow = "*" +scikit-learn = "*" +scipy = "*" +torch = ">=1.11.0" +tqdm = "*" +transformers = ">=4.41.0,<5.0.0" +typing_extensions = ">=4.5.0" + +[package.extras] +dev = ["accelerate (>=0.20.3)", "datasets", "peft", "pre-commit", "pytest", "pytest-cov"] +onnx = ["optimum[onnxruntime] (>=1.23.1)"] +onnx-gpu = ["optimum[onnxruntime-gpu] (>=1.23.1)"] +openvino = ["optimum-intel[openvino] (>=1.20.0)"] +train = ["accelerate (>=0.20.3)", "datasets"] + +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" or python_version >= \"3.12\"" +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.41" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "SQLAlchemy-2.0.41-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6854175807af57bdb6425e47adbce7d20a4d79bbfd6f6d6519cd10bb7109a7f8"}, + {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05132c906066142103b83d9c250b60508af556982a385d96c4eaa9fb9720ac2b"}, + {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4af17bda11e907c51d10686eda89049f9ce5669b08fbe71a29747f1e876036"}, + {file = "SQLAlchemy-2.0.41-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c0b0e5e1b5d9f3586601048dd68f392dc0cc99a59bb5faf18aab057ce00d00b2"}, + {file = "SQLAlchemy-2.0.41-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0b3dbf1e7e9bc95f4bac5e2fb6d3fb2f083254c3fdd20a1789af965caf2d2348"}, + {file = "SQLAlchemy-2.0.41-cp37-cp37m-win32.whl", hash = "sha256:1e3f196a0c59b0cae9a0cd332eb1a4bda4696e863f4f1cf84ab0347992c548c2"}, + {file = "SQLAlchemy-2.0.41-cp37-cp37m-win_amd64.whl", hash = "sha256:6ab60a5089a8f02009f127806f777fca82581c49e127f08413a66056bd9166dd"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-win32.whl", hash = "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda"}, + {file = "sqlalchemy-2.0.41-cp310-cp310-win_amd64.whl", hash = "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8"}, + {file = "sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6"}, + {file = "sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f"}, + {file = "sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90144d3b0c8b139408da50196c5cad2a6909b51b23df1f0538411cd23ffa45d3"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:023b3ee6169969beea3bb72312e44d8b7c27c75b347942d943cf49397b7edeb5"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725875a63abf7c399d4548e686debb65cdc2549e1825437096a0af1f7e374814"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81965cc20848ab06583506ef54e37cf15c83c7e619df2ad16807c03100745dea"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dd5ec3aa6ae6e4d5b5de9357d2133c07be1aff6405b136dad753a16afb6717dd"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ff8e80c4c4932c10493ff97028decfdb622de69cae87e0f127a7ebe32b4069c6"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-win32.whl", hash = "sha256:4d44522480e0bf34c3d63167b8cfa7289c1c54264c2950cc5fc26e7850967e45"}, + {file = "sqlalchemy-2.0.41-cp38-cp38-win_amd64.whl", hash = "sha256:81eedafa609917040d39aa9332e25881a8e7a0862495fcdf2023a9667209deda"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9a420a91913092d1e20c86a2f5f1fc85c1a8924dbcaf5e0586df8aceb09c9cc2"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:906e6b0d7d452e9a98e5ab8507c0da791856b2380fdee61b765632bb8698026f"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a373a400f3e9bac95ba2a06372c4fd1412a7cee53c37fc6c05f829bf672b8769"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087b6b52de812741c27231b5a3586384d60c353fbd0e2f81405a814b5591dc8b"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:34ea30ab3ec98355235972dadc497bb659cc75f8292b760394824fab9cf39826"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8280856dd7c6a68ab3a164b4a4b1c51f7691f6d04af4d4ca23d6ecf2261b7923"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-win32.whl", hash = "sha256:b50eab9994d64f4a823ff99a0ed28a6903224ddbe7fef56a6dd865eec9243440"}, + {file = "sqlalchemy-2.0.41-cp39-cp39-win_amd64.whl", hash = "sha256:5e22575d169529ac3e0a120cf050ec9daa94b6a9597993d1702884f6954a7d71"}, + {file = "sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576"}, + {file = "sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9"}, +] + +[package.dependencies] +greenlet = {version = ">=1", 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\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] +aioodbc = ["aioodbc", "greenlet (>=1)"] +aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (>=1)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "sympy" +version = "1.14.0" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, + {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "syrupy" +version = "4.9.1" +description = "Pytest Snapshot Test Utility" +optional = false +python-versions = ">=3.8.1" +groups = ["test"] +files = [ + {file = "syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda"}, + {file = "syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9.0.0" + +[[package]] +name = "tenacity" +version = "9.1.2" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, + {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, +] + +[[package]] +name = "tokenizers" +version = "0.21.2" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec"}, + {file = "tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f"}, + {file = "tokenizers-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a32cd81be21168bd0d6a0f0962d60177c447a1aa1b1e48fa6ec9fc728ee0b12"}, + {file = "tokenizers-0.21.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8bd8999538c405133c2ab999b83b17c08b7fc1b48c1ada2469964605a709ef91"}, + {file = "tokenizers-0.21.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e9944e61239b083a41cf8fc42802f855e1dca0f499196df37a8ce219abac6eb"}, + {file = "tokenizers-0.21.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:514cd43045c5d546f01142ff9c79a96ea69e4b5cda09e3027708cb2e6d5762ab"}, + {file = "tokenizers-0.21.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b9405822527ec1e0f7d8d2fdb287a5730c3a6518189c968254a8441b21faae"}, + {file = "tokenizers-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed9a4d51c395103ad24f8e7eb976811c57fbec2af9f133df471afcd922e5020"}, + {file = "tokenizers-0.21.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c41862df3d873665ec78b6be36fcc30a26e3d4902e9dd8608ed61d49a48bc19"}, + {file = "tokenizers-0.21.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed21dc7e624e4220e21758b2e62893be7101453525e3d23264081c9ef9a6d00d"}, + {file = "tokenizers-0.21.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:0e73770507e65a0e0e2a1affd6b03c36e3bc4377bd10c9ccf51a82c77c0fe365"}, + {file = "tokenizers-0.21.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:106746e8aa9014a12109e58d540ad5465b4c183768ea96c03cbc24c44d329958"}, + {file = "tokenizers-0.21.2-cp39-abi3-win32.whl", hash = "sha256:cabda5a6d15d620b6dfe711e1af52205266d05b379ea85a8a301b3593c60e962"}, + {file = "tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98"}, + {file = "tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77"}, +] + +[package.dependencies] +huggingface-hub = ">=0.16.4,<1.0" + +[package.extras] +dev = ["tokenizers[testing]"] +docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["test", "typing"] +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] +markers = {test = "python_full_version <= \"3.11.0a6\"", typing = "python_version < \"3.11\""} + +[[package]] +name = "torch" +version = "2.7.1" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +optional = false +python-versions = ">=3.9.0" +groups = ["test"] +files = [ + {file = "torch-2.7.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a103b5d782af5bd119b81dbcc7ffc6fa09904c423ff8db397a1e6ea8fd71508f"}, + {file = "torch-2.7.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:fe955951bdf32d182ee8ead6c3186ad54781492bf03d547d31771a01b3d6fb7d"}, + {file = "torch-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:885453d6fba67d9991132143bf7fa06b79b24352f4506fd4d10b309f53454162"}, + {file = "torch-2.7.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:d72acfdb86cee2a32c0ce0101606f3758f0d8bb5f8f31e7920dc2809e963aa7c"}, + {file = "torch-2.7.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:236f501f2e383f1cb861337bdf057712182f910f10aeaf509065d54d339e49b2"}, + {file = "torch-2.7.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:06eea61f859436622e78dd0cdd51dbc8f8c6d76917a9cf0555a333f9eac31ec1"}, + {file = "torch-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:8273145a2e0a3c6f9fd2ac36762d6ee89c26d430e612b95a99885df083b04e52"}, + {file = "torch-2.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:aea4fc1bf433d12843eb2c6b2204861f43d8364597697074c8d38ae2507f8730"}, + {file = "torch-2.7.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ea1e518df4c9de73af7e8a720770f3628e7f667280bce2be7a16292697e3fa"}, + {file = "torch-2.7.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c33360cfc2edd976c2633b3b66c769bdcbbf0e0b6550606d188431c81e7dd1fc"}, + {file = "torch-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d8bf6e1856ddd1807e79dc57e54d3335f2b62e6f316ed13ed3ecfe1fc1df3d8b"}, + {file = "torch-2.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:787687087412c4bd68d315e39bc1223f08aae1d16a9e9771d95eabbb04ae98fb"}, + {file = "torch-2.7.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:03563603d931e70722dce0e11999d53aa80a375a3d78e6b39b9f6805ea0a8d28"}, + {file = "torch-2.7.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d632f5417b6980f61404a125b999ca6ebd0b8b4bbdbb5fbbba44374ab619a412"}, + {file = "torch-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:23660443e13995ee93e3d844786701ea4ca69f337027b05182f5ba053ce43b38"}, + {file = "torch-2.7.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0da4f4dba9f65d0d203794e619fe7ca3247a55ffdcbd17ae8fb83c8b2dc9b585"}, + {file = "torch-2.7.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e08d7e6f21a617fe38eeb46dd2213ded43f27c072e9165dc27300c9ef9570934"}, + {file = "torch-2.7.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:30207f672328a42df4f2174b8f426f354b2baa0b7cca3a0adb3d6ab5daf00dc8"}, + {file = "torch-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:79042feca1c634aaf6603fe6feea8c6b30dfa140a6bbc0b973e2260c7e79a22e"}, + {file = "torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946"}, + {file = "torch-2.7.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:e0d81e9a12764b6f3879a866607c8ae93113cbcad57ce01ebde63eb48a576369"}, + {file = "torch-2.7.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:8394833c44484547ed4a47162318337b88c97acdb3273d85ea06e03ffff44998"}, + {file = "torch-2.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:df41989d9300e6e3c19ec9f56f856187a6ef060c3662fe54f4b6baf1fc90bd19"}, + {file = "torch-2.7.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:a737b5edd1c44a5c1ece2e9f3d00df9d1b3fb9541138bee56d83d38293fb6c9d"}, +] + +[package.dependencies] +filelock = "*" +fsspec = "*" +jinja2 = "*" +networkx = "*" +nvidia-cublas-cu12 = {version = "12.6.4.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.6.80", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.6.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.6.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "9.5.1.17", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.3.0.4", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufile-cu12 = {version = "1.11.1.6", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.7.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.7.1.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.5.4.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparselt-cu12 = {version = "0.6.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.26.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvjitlink-cu12 = {version = "12.6.85", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.6.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +setuptools = {version = "*", markers = "python_version >= \"3.12\""} +sympy = ">=1.13.3" +triton = {version = "3.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +typing-extensions = ">=4.10.0" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] +optree = ["optree (>=0.13.0)"] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["test"] +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "transformers" +version = "4.53.1" +description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" +optional = false +python-versions = ">=3.9.0" +groups = ["test"] +files = [ + {file = "transformers-4.53.1-py3-none-any.whl", hash = "sha256:c84f3c3e41c71fdf2c60c8a893e1cd31191b0cb463385f4c276302d2052d837b"}, + {file = "transformers-4.53.1.tar.gz", hash = "sha256:da5a9f66ad480bc2a7f75bc32eaf735fd20ac56af4325ca4ce994021ceb37710"}, +] + +[package.dependencies] +filelock = "*" +huggingface-hub = ">=0.30.0,<1.0" +numpy = ">=1.17" +packaging = ">=20.0" +pyyaml = ">=5.1" +regex = "!=2019.12.17" +requests = "*" +safetensors = ">=0.4.3" +tokenizers = ">=0.21,<0.22" +tqdm = ">=4.27" + +[package.extras] +accelerate = ["accelerate (>=0.26.0)"] +all = ["Pillow (>=10.0.1,<=15.0)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "accelerate (>=0.26.0)", "av", "codecarbon (>=2.8.1)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "kernels (>=0.6.1,<0.7)", "librosa", "num2words", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +benchmark = ["optimum-benchmark (>=0.3.0)"] +codecarbon = ["codecarbon (>=2.8.1)"] +deepspeed = ["accelerate (>=0.26.0)", "deepspeed (>=0.9.3)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.26.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "accelerate (>=0.26.0)", "av", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "kernels (>=0.6.1,<0.7)", "libcst", "librosa", "nltk (<=3.8.1)", "num2words", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "pandas (<2.3.0)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict_core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic_lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "pandas (<2.3.0)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "tf2onnx", "timeout-decorator", "tokenizers (>=0.21,<0.22)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "kenlm", "kernels (>=0.6.1,<0.7)", "libcst", "librosa", "nltk (<=3.8.1)", "num2words", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "pandas (<2.3.0)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict_core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic_lite (>=1.0.7)", "urllib3 (<2.0.0)"] +flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] +flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +ftfy = ["ftfy"] +hf-xet = ["hf_xet"] +hub-kernels = ["kernels (>=0.6.1,<0.7)"] +integrations = ["kernels (>=0.6.1,<0.7)", "optuna", "ray[tune] (>=2.7.0)", "sigopt"] +ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict_core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic_lite (>=1.0.7)"] +modelcreation = ["cookiecutter (==1.7.3)"] +natten = ["natten (>=0.14.6,<0.15.0)"] +num2words = ["num2words"] +onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] +onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] +open-telemetry = ["opentelemetry-api", "opentelemetry-exporter-otlp", "opentelemetry-sdk"] +optuna = ["optuna"] +quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "libcst", "pandas (<2.3.0)", "rich", "ruff (==0.11.2)", "urllib3 (<2.0.0)"] +ray = ["ray[tune] (>=2.7.0)"] +retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +ruff = ["ruff (==0.11.2)"] +sagemaker = ["sagemaker (>=2.31.0)"] +sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] +serving = ["fastapi", "pydantic", "starlette", "uvicorn"] +sigopt = ["sigopt"] +sklearn = ["scikit-learn"] +speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +tf = ["keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] +tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +tiktoken = ["blobfile", "tiktoken"] +timm = ["timm (<=1.0.11)"] +tokenizers = ["tokenizers (>=0.21,<0.22)"] +torch = ["accelerate (>=0.26.0)", "torch (>=2.1)"] +torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] +torchhub = ["filelock", "huggingface-hub (>=0.30.0,<1.0)", "importlib_metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "tqdm (>=4.27)"] +video = ["av"] +vision = ["Pillow (>=10.0.1,<=15.0)"] + +[[package]] +name = "triton" +version = "3.3.1" +description = "A language and compiler for custom Deep Learning operations" +optional = false +python-versions = "*" +groups = ["test"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "triton-3.3.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b74db445b1c562844d3cfad6e9679c72e93fdfb1a90a24052b03bb5c49d1242e"}, + {file = "triton-3.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b31e3aa26f8cb3cc5bf4e187bf737cbacf17311e1112b781d4a059353dfd731b"}, + {file = "triton-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9999e83aba21e1a78c1f36f21bce621b77bcaa530277a50484a7cb4a822f6e43"}, + {file = "triton-3.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b89d846b5a4198317fec27a5d3a609ea96b6d557ff44b56c23176546023c4240"}, + {file = "triton-3.3.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3198adb9d78b77818a5388bff89fa72ff36f9da0bc689db2f0a651a67ce6a42"}, + {file = "triton-3.3.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6139aeb04a146b0b8e0fbbd89ad1e65861c57cfed881f21d62d3cb94a36bab7"}, +] + +[package.dependencies] +setuptools = ">=40.8.0" + +[package.extras] +build = ["cmake (>=3.20)", "lit"] +tests = ["autopep8", "isort", "llnl-hatchet", "numpy", "pytest", "pytest-forked", "pytest-xdist", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + +[[package]] +name = "types-requests" +version = "2.32.4.20250611" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.9" +groups = ["typing"] +files = [ + {file = "types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072"}, + {file = "types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "test", "typing"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "typing-inspection" +version = "0.4.1" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main", "test", "typing"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +groups = ["test"] +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "yarl" +version = "1.20.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + +[[package]] +name = "zstandard" +version = "0.23.0" +description = "Zstandard bindings for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, + {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"}, + {file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"}, + {file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"}, + {file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"}, + {file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"}, + {file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"}, + {file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"}, + {file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"}, + {file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"}, + {file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"}, + {file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"}, + {file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"}, + {file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"}, + {file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"}, + {file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"}, + {file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"}, + {file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"}, + {file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"}, + {file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"}, + {file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"}, + {file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"}, + {file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"}, + {file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"}, + {file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"}, + {file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"}, + {file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"}, + {file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"}, + {file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"}, + {file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"}, + {file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"}, + {file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"}, + {file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"}, + {file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"}, +] + +[package.dependencies] +cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} + +[package.extras] +cffi = ["cffi (>=1.11)"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.9,<4.0" +content-hash = "ec5493b0e20638f45c739e4f8177a12ab62803869c982b3850ff0c67227259e2" diff --git a/libs/oracledb/pyproject.toml b/libs/oracledb/pyproject.toml new file mode 100644 index 0000000..597f821 --- /dev/null +++ b/libs/oracledb/pyproject.toml @@ -0,0 +1,99 @@ +[tool.poetry] +name = "langchain-oracledb" +version = "0.1.0" +description = "An integration package connecting Oracle Database and LangChain" +authors = [] +readme = "README.md" +repository = "https://github.com/oracle/langchain-oracle" +license = "UPL" + +[tool.poetry.urls] +"Source Code" = "https://github.com/oracle/langchain-oracle/tree/main/libs/oracledb" + +[tool.poetry.dependencies] +python = ">=3.9,<4.0" +langchain-core = ">=0.3.15,<0.4" +langchain-community = ">=0.3.0" +oracledb = ">=2.2.0" +pydantic = ">=2,<3" +numpy = ">=1.21,<3" + +[tool.poetry.group.test] +optional = true + +[tool.poetry.group.test.dependencies] +pytest = "^7.4.3" +pytest-cov = "^4.1.0" +syrupy = "^4.0.2" +pytest-asyncio = "^0.23.2" +pytest-watcher = "^0.3.4" +sentence-transformers = "^5.0.0" + +[tool.poetry.group.codespell] +optional = true + +[tool.poetry.group.codespell.dependencies] +codespell = "^2.2.6" + +[tool.poetry.group.test_integration] +optional = true + +[tool.poetry.group.test_integration.dependencies] + +[tool.poetry.group.lint] +optional = true + +[tool.poetry.group.lint.dependencies] +ruff = "^0.1.8" + +[tool.poetry.group.typing.dependencies] +mypy = "^1.7" +types-requests = "^2.28.11.5" + +[tool.poetry.group.dev] +optional = true + +[tool.poetry.group.dev.dependencies] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "T201", # print +] + +[tool.ruff.format] +docstring-code-format = true + +[tool.mypy] +ignore_missing_imports = "True" +disallow_untyped_defs = "True" + +[tool.coverage.run] +omit = ["tests/*"] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +# --strict-markers will raise errors on unknown marks. +# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks +# +# https://docs.pytest.org/en/7.1.x/reference/reference.html +# --strict-config any warnings encountered while parsing the `pytest` +# section of the configuration file raise errors. +# +# https://github.com/tophat/syrupy +# --snapshot-warn-unused Prints a warning on unused snapshots rather than fail the test suite. +addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5 --cov=langchain_oracledb" +# Registering custom markers. +# https://docs.pytest.org/en/7.1.x/example/markers.html#registering-markers +markers = [ + "requires: mark tests as requiring a specific library", + "asyncio: mark tests as requiring asyncio", + "compile: mark placeholder test used to compile integration tests without running them", + "scheduled: mark tests to run in scheduled testing", +] +asyncio_mode = "auto" diff --git a/libs/oracledb/scripts/check_imports.py b/libs/oracledb/scripts/check_imports.py new file mode 100644 index 0000000..2f65ab3 --- /dev/null +++ b/libs/oracledb/scripts/check_imports.py @@ -0,0 +1,20 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +import sys +import traceback +from importlib.machinery import SourceFileLoader + +if __name__ == "__main__": + files = sys.argv[1:] + has_failure = False + for file in files: + try: + SourceFileLoader("x", file).load_module() + except Exception: + has_faillure = True + print(file) # noqa: T201 + traceback.print_exc() + print() # noqa: T201 + + sys.exit(1 if has_failure else 0) diff --git a/libs/oracledb/scripts/lint_imports.sh b/libs/oracledb/scripts/lint_imports.sh new file mode 100755 index 0000000..19ccec1 --- /dev/null +++ b/libs/oracledb/scripts/lint_imports.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -eu + +# Initialize a variable to keep track of errors +errors=0 + +# make sure not importing from langchain, langchain_experimental, or langchain_community +git --no-pager grep '^from langchain\.' . && errors=$((errors+1)) +git --no-pager grep '^from langchain_experimental\.' . && errors=$((errors+1)) +git --no-pager grep '^from langchain_community\.' . && errors=$((errors+1)) + +# Decide on an exit status based on the errors +if [ "$errors" -gt 0 ]; then + exit 1 +else + exit 0 +fi diff --git a/libs/oracledb/tests/__init__.py b/libs/oracledb/tests/__init__.py new file mode 100644 index 0000000..09c1843 --- /dev/null +++ b/libs/oracledb/tests/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ diff --git a/libs/oracledb/tests/integration_tests/__init__.py b/libs/oracledb/tests/integration_tests/__init__.py new file mode 100644 index 0000000..9d558f0 --- /dev/null +++ b/libs/oracledb/tests/integration_tests/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ diff --git a/libs/oracledb/tests/integration_tests/document_loaders/__init__.py b/libs/oracledb/tests/integration_tests/document_loaders/__init__.py new file mode 100644 index 0000000..9d558f0 --- /dev/null +++ b/libs/oracledb/tests/integration_tests/document_loaders/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ diff --git a/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py b/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py new file mode 100644 index 0000000..32b34a6 --- /dev/null +++ b/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py @@ -0,0 +1,445 @@ +# Authors: +# Sudhir Kumar (sudhirkk) +# +# ----------------------------------------------------------------------------- +# test_oracleds.py +# ----------------------------------------------------------------------------- +import sys + +from langchain_oracledb.document_loaders.oracleai import ( + OracleDocLoader, + OracleTextSplitter, +) +from langchain_oracledb.utilities.oracleai import OracleSummary +from langchain_oracledb.vectorstores.oraclevs import ( + _table_exists, + drop_table_purge, +) + +uname = "" +passwd = "" +v_dsn = "" + + +### Test loader ##### +def test_loader_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + # oracle connection + connection = oracledb.connect(user=uname, password=passwd, dsn=v_dsn) + cursor = connection.cursor() + + if _table_exists(connection, "LANGCHAIN_DEMO"): + drop_table_purge(connection, "LANGCHAIN_DEMO") + + cursor.execute("CREATE TABLE langchain_demo(id number, text varchar2(25))") + + rows = [ + (1, "First"), + (2, "Second"), + (3, "Third"), + (4, "Fourth"), + (5, "Fifth"), + (6, "Sixth"), + (7, "Seventh"), + ] + + cursor.executemany("insert into LANGCHAIN_DEMO(id, text) values (:1, :2)", rows) + + connection.commit() + + # local file, local directory, database column + loader_params = { + "owner": uname, + "tablename": "LANGCHAIN_DEMO", + "colname": "TEXT", + } + + # instantiate + loader = OracleDocLoader(conn=connection, params=loader_params) + + # load + docs = loader.load() + + # verify + if len(docs) == 0: + sys.exit(1) + + if _table_exists(connection, "LANGCHAIN_DEMO"): + drop_table_purge(connection, "LANGCHAIN_DEMO") + + except Exception: + sys.exit(1) + + try: + # expectation : ORA-00942 + loader_params = { + "owner": uname, + "tablename": "COUNTRIES1", + "colname": "COUNTRY_NAME", + } + + # instantiate + loader = OracleDocLoader(conn=connection, params=loader_params) + + # load + docs = loader.load() + if len(docs) == 0: + pass + + except Exception: + pass + + try: + # expectation : file "SUDHIR" doesn't exist. + loader_params = {"file": "SUDHIR"} + + # instantiate + loader = OracleDocLoader(conn=connection, params=loader_params) + + # load + docs = loader.load() + if len(docs) == 0: + pass + + except Exception: + pass + + try: + # expectation : path "SUDHIR" doesn't exist. + loader_params = {"dir": "SUDHIR"} + + # instantiate + loader = OracleDocLoader(conn=connection, params=loader_params) + + # load + docs = loader.load() + if len(docs) == 0: + pass + + except Exception: + pass + + +### Test splitter #### +def test_splitter_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + # oracle connection + connection = oracledb.connect(user=uname, password=passwd, dsn=v_dsn) + doc = """Langchain is a wonderful framework to load, split, chunk + and embed your data!!""" + + # by words , max = 1000 + splitter_params = { + "by": "words", + "max": "1000", + "overlap": "200", + "split": "custom", + "custom_list": [","], + "extended": "true", + "normalize": "all", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + sys.exit(1) + + # by chars , max = 4000 + splitter_params = { + "by": "chars", + "max": "4000", + "overlap": "800", + "split": "NEWLINE", + "normalize": "all", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + sys.exit(1) + + # by words , max = 10 + splitter_params = { + "by": "words", + "max": "10", + "overlap": "2", + "split": "SENTENCE", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + sys.exit(1) + + # by chars , max = 50 + splitter_params = { + "by": "chars", + "max": "50", + "overlap": "10", + "split": "SPACE", + "normalize": "all", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + sys.exit(1) + + except Exception: + sys.exit(1) + + try: + # ORA-20003: invalid value xyz for BY parameter + splitter_params = {"by": "xyz"} + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + pass + + except Exception: + pass + + try: + # Expectation: ORA-30584: invalid text chunking MAXIMUM - '10' + splitter_params = { + "by": "chars", + "max": "10", + "overlap": "2", + "split": "SPACE", + "normalize": "all", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + pass + + except Exception: + pass + + try: + # Expectation: ORA-30584: invalid text chunking MAXIMUM - '5' + splitter_params = { + "by": "words", + "max": "5", + "overlap": "2", + "split": "SPACE", + "normalize": "all", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + pass + + except Exception: + pass + + try: + # Expectation: ORA-30586: invalid text chunking SPLIT BY - SENTENCE + splitter_params = { + "by": "words", + "max": "50", + "overlap": "2", + "split": "SENTENCE", + "normalize": "all", + } + + # instantiate + splitter = OracleTextSplitter(conn=connection, params=splitter_params) + + # generate chunks + chunks = splitter.split_text(doc) + + # verify + if len(chunks) == 0: + pass + + except Exception: + pass + + +#### Test summary #### +def test_summary_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + # oracle connection + connection = oracledb.connect(user=uname, password=passwd, dsn=v_dsn) + + # provider : Database, glevel : Paragraph + summary_params = { + "provider": "database", + "glevel": "paragraph", + "numParagraphs": 2, + "language": "english", + } + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + + doc = """It was 7 minutes after midnight. The dog was lying on the grass in + of the lawn in front of Mrs Shears house. Its eyes were closed. It + was running on its side, the way dogs run when they think they are + cat in a dream. But the dog was not running or asleep. The dog was dead. + was a garden fork sticking out of the dog. The points of the fork must + gone all the way through the dog and into the ground because the fork + not fallen over. I decided that the dog was probably killed with the + because I could not see any other wounds in the dog and I do not think + would stick a garden fork into a dog after it had died for some other + like cancer for example, or a road accident. But I could not be certain""" + + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + sys.exit(1) + + # provider : Database, glevel : Sentence + summary_params = {"provider": "database", "glevel": "Sentence"} + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + sys.exit(1) + + # provider : Database, glevel : P + summary_params = {"provider": "database", "glevel": "P"} + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + sys.exit(1) + + # provider : Database, glevel : S + summary_params = { + "provider": "database", + "glevel": "S", + "numParagraphs": 16, + "language": "english", + } + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + sys.exit(1) + + # provider : Database, glevel : S, doc = ' ' + summary_params = {"provider": "database", "glevel": "S", "numParagraphs": 2} + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + + doc = " " + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + sys.exit(1) + + except Exception: + sys.exit(1) + + try: + # Expectation : DRG-11002: missing value for PROVIDER + summary_params = {"provider": "database1", "glevel": "S"} + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + pass + + except Exception: + pass + + try: + # Expectation : DRG-11425: gist level SUDHIR is invalid, + # DRG-11427: valid gist level values are S, P + summary_params = {"provider": "database", "glevel": "SUDHIR"} + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + pass + + except Exception: + pass + + try: + # Expectation : DRG-11441: gist numParagraphs -2 is invalid + summary_params = {"provider": "database", "glevel": "S", "numParagraphs": -2} + + # summary + summary = OracleSummary(conn=connection, params=summary_params) + summaries = summary.get_summary(doc) + + # verify + if len(summaries) == 0: + pass + + except Exception: + pass diff --git a/libs/oracledb/tests/integration_tests/vectorstores/__init__.py b/libs/oracledb/tests/integration_tests/vectorstores/__init__.py new file mode 100644 index 0000000..9d558f0 --- /dev/null +++ b/libs/oracledb/tests/integration_tests/vectorstores/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py new file mode 100644 index 0000000..19ead0f --- /dev/null +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -0,0 +1,956 @@ +"""Test Oracle AI Vector Search functionality.""" + +# import required modules +import sys +import threading + +from langchain_community.embeddings import HuggingFaceEmbeddings +from langchain_community.vectorstores.utils import DistanceStrategy + +from langchain_oracledb.vectorstores.oraclevs import ( + OracleVS, + _create_table, + _index_exists, + _table_exists, + create_index, + drop_index_if_exists, + drop_table_purge, +) + +username = "" +password = "" +dsn = "" + + +############################ +####### table_exists ####### +############################ +def test_table_exists_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + # 1. Existing Table:(all capital letters) + # expectation:True + _table_exists(connection, "V$TRANSACTION") + + # 2. Existing Table:(all small letters) + # expectation:True + _table_exists(connection, "v$transaction") + + # 3. Non-Existing Table + # expectation:false + _table_exists(connection, "Hello") + + # 4. Invalid Table Name + # Expectation:ORA-00903: invalid table name + try: + _table_exists(connection, "123") + except Exception: + pass + + # 5. Empty String + # Expectation:ORA-00903: invalid table name + try: + _table_exists(connection, "") + except Exception: + pass + + # 6. Special Character + # Expectation:ORA-00911: #: invalid character after FROM + try: + _table_exists(connection, "##4") + except Exception: + pass + + # 7. Table name length > 128 + # Expectation:ORA-00972: The identifier XXXXXXXXXX...XXXXXXXXXX... + # exceeds the maximum length of 128 bytes. + try: + _table_exists(connection, "x" * 129) + except Exception: + pass + + # 8. + # Expectation:True + _create_table(connection, "TB1", 65535) + + # 9. Toggle Case (like TaBlE) + # Expectation:True + _table_exists(connection, "Tb1") + drop_table_purge(connection, "TB1") + + # 10. Table_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" + # Expectation:True + _create_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) + _table_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + drop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + + +############################ +####### create_table ####### +############################ + + +def test_create_table_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + + # 1. New table - HELLO + # Dimension - 100 + # Expectation:table is created + _create_table(connection, "HELLO", 100) + + # 2. Existing table name + # HELLO + # Dimension - 110 + # Expectation:Nothing happens + _create_table(connection, "HELLO", 110) + drop_table_purge(connection, "HELLO") + + # 3. New Table - 123 + # Dimension - 100 + # Expectation:ORA-00903: invalid table name + try: + _create_table(connection, "123", 100) + drop_table_purge(connection, "123") + except Exception: + pass + + # 4. New Table - Hello123 + # Dimension - 65535 + # Expectation:table is created + _create_table(connection, "Hello123", 65535) + drop_table_purge(connection, "Hello123") + + # 5. New Table - T1 + # Dimension - 65536 + # Expectation:ORA-51801: VECTOR column type specification + # has an unsupported dimension count ('65536'). + try: + _create_table(connection, "T1", 65536) + drop_table_purge(connection, "T1") + except Exception: + pass + + # 6. New Table - T1 + # Dimension - 0 + # Expectation:ORA-51801: VECTOR column type specification has + # an unsupported dimension count (0). + try: + _create_table(connection, "T1", 0) + drop_table_purge(connection, "T1") + except Exception: + pass + + # 7. New Table - T1 + # Dimension - -1 + # Expectation:ORA-51801: VECTOR column type specification has + # an unsupported dimension count ('-'). + try: + _create_table(connection, "T1", -1) + drop_table_purge(connection, "T1") + except Exception: + pass + + # 8. New Table - T2 + # Dimension - '1000' + # Expectation:table is created + _create_table(connection, "T2", int("1000")) + drop_table_purge(connection, "T2") + + # 9. New Table - T3 + # Dimension - 100 passed as a variable + # Expectation:table is created + val = 100 + _create_table(connection, "T3", val) + drop_table_purge(connection, "T3") + + # 10. + # Expectation:ORA-00922: missing or invalid option + val2 = """H + ello""" + try: + _create_table(connection, val2, 545) + drop_table_purge(connection, val2) + except Exception: + pass + + # 11. New Table - เคนเคฟเคจเฅเคฆเฅ€ + # Dimension - 545 + # Expectation:table is created + _create_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) + drop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + + # 12. + # Expectation:failure - user does not exist + try: + _create_table(connection, "U1.TB4", 128) + drop_table_purge(connection, "U1.TB4") + except Exception: + pass + + # 13. + # Expectation:table is created + _create_table(connection, '"T5"', 128) + drop_table_purge(connection, '"T5"') + + # 14. Toggle Case + # Expectation:table creation fails + try: + _create_table(connection, "TaBlE", 128) + drop_table_purge(connection, "TaBlE") + except Exception: + pass + + # 15. table_name as empty_string + # Expectation: ORA-00903: invalid table name + try: + _create_table(connection, "", 128) + drop_table_purge(connection, "") + _create_table(connection, '""', 128) + drop_table_purge(connection, '""') + except Exception: + pass + + # 16. Arithmetic Operations in dimension parameter + # Expectation:table is created + n = 1 + _create_table(connection, "T10", n + 500) + drop_table_purge(connection, "T10") + + # 17. String Operations in table_name&dimension parameter + # Expectation:table is created + _create_table(connection, "YaSh".replace("aS", "ok"), 500) + drop_table_purge(connection, "YaSh".replace("aS", "ok")) + + +################################## +####### create_hnsw_index ####### +################################## + + +def test_create_hnsw_index_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + # 1. Table_name - TB1 + # New Index + # distance_strategy - DistanceStrategy.Dot_product + # Expectation:Index created + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + vs = OracleVS(connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs) + + # 2. Creating same index again + # Table_name - TB1 + # Expectation:Nothing happens + try: + create_index(connection, vs) + drop_index_if_exists(connection, "HNSW") + except Exception: + pass + drop_table_purge(connection, "TB1") + + # 3. Create index with following parameters: + # idx_name - hnsw_idx2 + # idx_type - HNSW + # Expectation:Index created + vs = OracleVS(connection, model1, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"}) + drop_index_if_exists(connection, "hnsw_idx2") + drop_table_purge(connection, "TB2") + + # 4. Table Name - TB1 + # idx_name - "เคนเคฟเคจเฅเคฆเฅ€" + # idx_type - HNSW + # Expectation:Index created + try: + vs = OracleVS(connection, model1, "TB3", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"}) + drop_index_if_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + except Exception: + pass + drop_table_purge(connection, "TB3") + + # 5. idx_name passed empty + # Expectation:ORA-01741: illegal zero-length identifier + try: + vs = OracleVS(connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": '""', "idx_type": "HNSW"}) + drop_index_if_exists(connection, '""') + except Exception: + pass + drop_table_purge(connection, "TB4") + + # 6. idx_type left empty + # Expectation:Index created + try: + vs = OracleVS(connection, model1, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "Hello", "idx_type": ""}) + drop_index_if_exists(connection, "Hello") + except Exception: + pass + drop_table_purge(connection, "TB5") + + # 7. efconstruction passed as parameter but not neighbours + # Expectation:Index created + vs = OracleVS(connection, model1, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index( + connection, + vs, + params={"idx_name": "idx11", "efConstruction": 100, "idx_type": "HNSW"}, + ) + drop_index_if_exists(connection, "idx11") + drop_table_purge(connection, "TB7") + + # 8. efconstruction passed as parameter as well as neighbours + # (for this idx_type parameter is also necessary) + # Expectation:Index created + vs = OracleVS(connection, model1, "TB8", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index( + connection, + vs, + params={ + "idx_name": "idx11", + "efConstruction": 100, + "neighbors": 80, + "idx_type": "HNSW", + }, + ) + drop_index_if_exists(connection, "idx11") + drop_table_purge(connection, "TB8") + + # 9. Limit of Values for(integer values): + # parallel + # efConstruction + # Neighbors + # Accuracy + # 0 + # Expectation:Index created + vs = OracleVS(connection, model1, "TB15", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index( + connection, + vs, + params={ + "idx_name": "idx11", + "efConstruction": 200, + "neighbors": 100, + "idx_type": "HNSW", + "parallel": 8, + "accuracy": 10, + }, + ) + drop_index_if_exists(connection, "idx11") + drop_table_purge(connection, "TB15") + + # 11. index_name as + # Expectation:U1 not present + try: + vs = OracleVS( + connection, model1, "U1.TB16", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + create_index( + connection, + vs, + params={ + "idx_name": "U1.idx11", + "efConstruction": 200, + "neighbors": 100, + "idx_type": "HNSW", + "parallel": 8, + "accuracy": 10, + }, + ) + drop_index_if_exists(connection, "U1.idx11") + drop_table_purge(connection, "TB16") + except Exception: + pass + + # 12. Index_name size >129 + # Expectation:Index not created + try: + vs = OracleVS(connection, model1, "TB17", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "x" * 129, "idx_type": "HNSW"}) + drop_index_if_exists(connection, "x" * 129) + except Exception: + pass + drop_table_purge(connection, "TB17") + + # 13. Index_name size 128 + # Expectation:Index created + vs = OracleVS(connection, model1, "TB18", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "x" * 128, "idx_type": "HNSW"}) + drop_index_if_exists(connection, "x" * 128) + drop_table_purge(connection, "TB18") + + +################################## +####### index_exists ############# +################################## + + +def test_index_exists_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + # 1. Existing Index:(all capital letters) + # Expectation:true + vs = OracleVS(connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "idx11", "idx_type": "HNSW"}) + _index_exists(connection, "IDX11") + + # 2. Existing Table:(all small letters) + # Expectation:true + _index_exists(connection, "idx11") + + # 3. Non-Existing Index + # Expectation:False + _index_exists(connection, "Hello") + + # 4. Invalid Index Name + # Expectation:Error + try: + _index_exists(connection, "123") + except Exception: + pass + + # 5. Empty String + # Expectation:Error + try: + _index_exists(connection, "") + except Exception: + pass + try: + _index_exists(connection, "") + except Exception: + pass + + # 6. Special Character + # Expectation:Error + try: + _index_exists(connection, "##4") + except Exception: + pass + + # 7. Index name length > 128 + # Expectation:Error + try: + _index_exists(connection, "x" * 129) + except Exception: + pass + + # 8. + # Expectation:true + _index_exists(connection, "U1.IDX11") + + # 9. Toggle Case (like iDx11) + # Expectation:true + _index_exists(connection, "IdX11") + + # 10. Index_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" + # Expectation:true + drop_index_if_exists(connection, "idx11") + try: + create_index(connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"}) + _index_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + except Exception: + pass + drop_table_purge(connection, "TB1") + + +################################## +####### add_texts ################ +################################## + + +def test_add_texts_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + # 1. Add 2 records to table + # Expectation:Successful + texts = ["Rohan", "Shailendra"] + metadata = [ + {"id": "100", "link": "Document Example Test 1"}, + {"id": "101", "link": "Document Example Test 2"}, + ] + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = OracleVS(connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) + vs_obj.add_texts(texts, metadata) + drop_table_purge(connection, "TB1") + + # 2. Add record but metadata is not there + # Expectation:An exception occurred :: Either specify an 'ids' list or + # 'metadatas' with an 'id' attribute for each element. + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = OracleVS(connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE) + texts2 = ["Sri Ram", "Krishna"] + vs_obj.add_texts(texts2) + drop_table_purge(connection, "TB2") + + # 3. Add record with ids option + # ids are passed as string + # ids are passed as empty string + # ids are passed as multi-line string + # ids are passed as "" + # Expectations: + # Successful + # Successful + # Successful + # Successful + + vs_obj = OracleVS(connection, model, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE) + ids3 = ["114", "124"] + vs_obj.add_texts(texts2, ids=ids3) + drop_table_purge(connection, "TB4") + + vs_obj = OracleVS(connection, model, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE) + ids4 = ["", "134"] + vs_obj.add_texts(texts2, ids=ids4) + drop_table_purge(connection, "TB5") + + vs_obj = OracleVS(connection, model, "TB6", DistanceStrategy.EUCLIDEAN_DISTANCE) + ids5 = [ + """Good afternoon + my friends""", + "India", + ] + vs_obj.add_texts(texts2, ids=ids5) + drop_table_purge(connection, "TB6") + + vs_obj = OracleVS(connection, model, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE) + ids6 = ['"Good afternoon"', '"India"'] + vs_obj.add_texts(texts2, ids=ids6) + drop_table_purge(connection, "TB7") + + # 4. Add records with ids and metadatas + # Expectation:Successful + vs_obj = OracleVS(connection, model, "TB8", DistanceStrategy.EUCLIDEAN_DISTANCE) + texts3 = ["Sri Ram 6", "Krishna 6"] + ids7 = ["1", "2"] + metadata = [ + {"id": "102", "link": "Document Example", "stream": "Science"}, + {"id": "104", "link": "Document Example 45"}, + ] + vs_obj.add_texts(texts3, metadata, ids=ids7) + drop_table_purge(connection, "TB8") + + # 5. Add 10000 records + # Expectation:Successful + vs_obj = OracleVS(connection, model, "TB9", DistanceStrategy.EUCLIDEAN_DISTANCE) + texts4 = ["Sri Ram{0}".format(i) for i in range(1, 10000)] + ids8 = ["Hello{0}".format(i) for i in range(1, 10000)] + vs_obj.add_texts(texts4, ids=ids8) + drop_table_purge(connection, "TB9") + + # 6. Add 2 different record concurrently + # Expectation:Successful + def add(val: str) -> None: + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/all-mpnet-base-v2" + ) + vs_obj = OracleVS( + connection, model, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts5 = [val] + ids9 = texts5 + vs_obj.add_texts(texts5, ids=ids9) + + thread_1 = threading.Thread(target=add, args=("Sri Ram")) + thread_2 = threading.Thread(target=add, args=("Sri Krishna")) + thread_1.start() + thread_2.start() + thread_1.join() + thread_2.join() + drop_table_purge(connection, "TB10") + + # 7. Add 2 same record concurrently + # Expectation:Successful, For one of the insert,get primary key violation error + def add1(val: str) -> None: + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/all-mpnet-base-v2" + ) + vs_obj = OracleVS( + connection, model, "TB11", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts = [val] + ids10 = texts + vs_obj.add_texts(texts, ids=ids10) + + try: + thread_1 = threading.Thread(target=add1, args=("Sri Ram")) + thread_2 = threading.Thread(target=add1, args=("Sri Ram")) + thread_1.start() + thread_2.start() + thread_1.join() + thread_2.join() + except Exception: + pass + drop_table_purge(connection, "TB11") + + # 8. create object with table name of type + # Expectation:U1 does not exist + try: + vs_obj = OracleVS(connection, model, "U1.TB14", DistanceStrategy.DOT_PRODUCT) + for i in range(1, 10): + texts7 = ["Yash{0}".format(i)] + ids13 = ["1234{0}".format(i)] + vs_obj.add_texts(texts7, ids=ids13) + drop_table_purge(connection, "TB14") + except Exception: + pass + + +################################## +####### embed_documents(text) #### +################################## +def test_embed_documents_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + # 1. String Example-'Sri Ram' + # Expectation:Vector Printed + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = OracleVS(connection, model, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE) + + # 4. List + # Expectation:Vector Printed + vs_obj._embed_documents(["hello", "yash"]) + drop_table_purge(connection, "TB7") + + +################################## +####### embed_query(text) ######## +################################## +def test_embed_query_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + # 1. String + # Expectation:Vector printed + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = OracleVS(connection, model, "TB8", DistanceStrategy.EUCLIDEAN_DISTANCE) + vs_obj._embed_query("Sri Ram") + drop_table_purge(connection, "TB8") + + # 3. Empty string + # Expectation:[] + vs_obj._embed_query("") + + +################################## +####### create_index ############# +################################## +def test_create_index_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + # 1. No optional parameters passed + # Expectation:Successful + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + vs = OracleVS(connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs) + drop_index_if_exists(connection, "HNSW") + drop_table_purge(connection, "TB1") + + # 2. ivf index + # Expectation:Successful + vs = OracleVS(connection, model1, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, {"idx_type": "IVF", "idx_name": "IVF"}) + drop_index_if_exists(connection, "IVF") + drop_table_purge(connection, "TB2") + + # 3. ivf index with neighbour_part passed as parameter + # Expectation:Successful + vs = OracleVS(connection, model1, "TB3", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, {"idx_type": "IVF", "neighbor_part": 10}) + drop_index_if_exists(connection, "IVF") + drop_table_purge(connection, "TB3") + + # 4. ivf index with neighbour_part and accuracy passed as parameter + # Expectation:Successful + vs = OracleVS(connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index( + connection, vs, {"idx_type": "IVF", "neighbor_part": 10, "accuracy": 90} + ) + drop_index_if_exists(connection, "IVF") + drop_table_purge(connection, "TB4") + + # 5. ivf index with neighbour_part and parallel passed as parameter + # Expectation:Successful + vs = OracleVS(connection, model1, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index( + connection, vs, {"idx_type": "IVF", "neighbor_part": 10, "parallel": 90} + ) + drop_index_if_exists(connection, "IVF") + drop_table_purge(connection, "TB5") + + # 6. ivf index and then perform dml(insert) + # Expectation:Successful + vs = OracleVS(connection, model1, "TB6", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, {"idx_type": "IVF", "idx_name": "IVF"}) + texts = ["Sri Ram", "Krishna"] + vs.add_texts(texts) + # perform delete + vs.delete(["hello"]) + drop_index_if_exists(connection, "IVF") + drop_table_purge(connection, "TB6") + + # 7. ivf index with neighbour_part,parallel and accuracy passed as parameter + # Expectation:Successful + vs = OracleVS(connection, model1, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index( + connection, + vs, + {"idx_type": "IVF", "neighbor_part": 10, "parallel": 90, "accuracy": 99}, + ) + drop_index_if_exists(connection, "IVF") + drop_table_purge(connection, "TB7") + + +################################## +####### perform_search ########### +################################## +def test_perform_search_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + vs_1 = OracleVS(connection, model1, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE) + vs_2 = OracleVS(connection, model1, "TB11", DistanceStrategy.DOT_PRODUCT) + vs_3 = OracleVS(connection, model1, "TB12", DistanceStrategy.COSINE) + vs_4 = OracleVS(connection, model1, "TB13", DistanceStrategy.EUCLIDEAN_DISTANCE) + vs_5 = OracleVS(connection, model1, "TB14", DistanceStrategy.DOT_PRODUCT) + vs_6 = OracleVS(connection, model1, "TB15", DistanceStrategy.COSINE) + + # vector store lists: + vs_list = [vs_1, vs_2, vs_3, vs_4, vs_5, vs_6] + + for i, vs in enumerate(vs_list, start=1): + # insert data + texts = ["Yash", "Varanasi", "Yashaswi", "Mumbai", "BengaluruYash"] + metadatas = [ + {"id": "hello"}, + {"id": "105"}, + {"id": "106"}, + {"id": "yash"}, + {"id": "108"}, + ] + + vs.add_texts(texts, metadatas) + + # create index + if i == 1 or i == 2 or i == 3: + create_index(connection, vs, {"idx_type": "HNSW", "idx_name": f"IDX1{i}"}) + else: + create_index(connection, vs, {"idx_type": "IVF", "idx_name": f"IDX1{i}"}) + + # perform search + query = "YashB" + + filter = {"id": ["106", "108", "yash"]} + + # similarity_searh without filter + vs.similarity_search(query, 2) + + # similarity_searh with filter + vs.similarity_search(query, 2, filter=filter) + + # Similarity search with relevance score + vs.similarity_search_with_score(query, 2) + + # Similarity search with relevance score with filter + vs.similarity_search_with_score(query, 2, filter=filter) + + # Max marginal relevance search + vs.max_marginal_relevance_search(query, 2, fetch_k=20, lambda_mult=0.5) + + # Max marginal relevance search with filter + vs.max_marginal_relevance_search( + query, 2, fetch_k=20, lambda_mult=0.5, filter=filter + ) + + drop_table_purge(connection, "TB10") + drop_table_purge(connection, "TB11") + drop_table_purge(connection, "TB12") + drop_table_purge(connection, "TB13") + drop_table_purge(connection, "TB14") + drop_table_purge(connection, "TB15") diff --git a/libs/oracledb/tests/unit_tests/__init__.py b/libs/oracledb/tests/unit_tests/__init__.py new file mode 100644 index 0000000..083e045 --- /dev/null +++ b/libs/oracledb/tests/unit_tests/__init__.py @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +"""All unit tests (lightweight tests).""" + +from typing import Any + + +def assert_all_importable(module: Any) -> None: + for attr in module.__all__: + getattr(module, attr) diff --git a/libs/oracledb/tests/unit_tests/document_loaders/__init__.py b/libs/oracledb/tests/unit_tests/document_loaders/__init__.py new file mode 100644 index 0000000..9d558f0 --- /dev/null +++ b/libs/oracledb/tests/unit_tests/document_loaders/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ diff --git a/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py b/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py new file mode 100644 index 0000000..34d86f0 --- /dev/null +++ b/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py @@ -0,0 +1,56 @@ +from typing import Dict, List +from unittest.mock import MagicMock, patch + +from langchain_core.documents import Document + +from langchain_oracledb.document_loaders.oracleadb_loader import ( + OracleAutonomousDatabaseLoader, +) + + +def raw_docs() -> List[Dict]: + return [ + {"FIELD1": "1", "FIELD_JSON": {"INNER_FIELD1": "1", "INNER_FIELD2": "1"}}, + {"FIELD1": "2", "FIELD_JSON": {"INNER_FIELD1": "2", "INNER_FIELD2": "2"}}, + {"FIELD1": "3", "FIELD_JSON": {"INNER_FIELD1": "3", "INNER_FIELD2": "3"}}, + ] + + +def expected_documents() -> List[Document]: + return [ + Document( + page_content="{'FIELD1': '1', 'FIELD_JSON': " + "{'INNER_FIELD1': '1', 'INNER_FIELD2': '1'}}", + metadata={"FIELD1": "1"}, + ), + Document( + page_content="{'FIELD1': '2', 'FIELD_JSON': " + "{'INNER_FIELD1': '2', 'INNER_FIELD2': '2'}}", + metadata={"FIELD1": "2"}, + ), + Document( + page_content="{'FIELD1': '3', 'FIELD_JSON': " + "{'INNER_FIELD1': '3', 'INNER_FIELD2': '3'}}", + metadata={"FIELD1": "3"}, + ), + ] + + +@patch( + "langchain_oracledb.document_loaders.oracleadb_loader.OracleAutonomousDatabaseLoader._run_query" +) +def test_oracle_loader_load(mock_query: MagicMock) -> None: + """Test oracleDB loader load function.""" + + mock_query.return_value = raw_docs() + loader = OracleAutonomousDatabaseLoader( + query="Test query", + user="Test user", + password="Test password", + connection_string="Test connection string", + metadata=["FIELD1"], + ) + + documents = loader.load() + + assert documents == expected_documents() diff --git a/libs/oracledb/tests/unit_tests/test_imports.py b/libs/oracledb/tests/unit_tests/test_imports.py new file mode 100644 index 0000000..f1ace38 --- /dev/null +++ b/libs/oracledb/tests/unit_tests/test_imports.py @@ -0,0 +1,18 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + +import glob +import importlib +from pathlib import Path + + +def test_importable_all() -> None: + for path in glob.glob("../langchain_oci/*"): + relative_path = Path(path).parts[-1] + if relative_path.endswith(".typed"): + continue + module_name = relative_path.split(".")[0] + module = importlib.import_module("langchain_oci." + module_name) + all_ = getattr(module, "__all__", []) + for cls_ in all_: + getattr(module, cls_) From 6d997ec0af9ef33d2228b7bda1c60d0bf83cdf46 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 10 Jul 2025 14:23:07 +0000 Subject: [PATCH 02/12] Add support for oracle dsl --- .../vectorstores/oraclevs.py | 179 +++++++++++++----- .../vectorstores/test_oraclevs.py | 124 ++++++++++++ 2 files changed, 257 insertions(+), 46 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index 3ddd8c9..9503ce8 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -20,6 +20,7 @@ Optional, Tuple, Type, + TypedDict, TypeVar, Union, cast, @@ -52,6 +53,56 @@ T = TypeVar("T", bound=Callable[..., Any]) +class FilterCondition(TypedDict): + key: str + oper: str + value: str + + +class FilterGroup(TypedDict, total=False): + _and: Optional[List[Union["FilterCondition", "FilterGroup"]]] + _or: Optional[List[Union["FilterCondition", "FilterGroup"]]] + + +def _convert_oper_to_sql(oper: str) -> str: + oper_map = {"EQ": "==", "GT": ">", "LT": "<", "GTE": ">=", "LTE": "<="} + if oper not in oper_map: + raise ValueError("Filter operation {} not supported".format(oper)) + return oper_map.get(oper, "==") + + +def _generate_condition(condition: FilterCondition) -> str: + key = condition["key"] + oper = _convert_oper_to_sql(condition["oper"]) + value = condition["value"] + if isinstance(value, str): + value = f'"{value}"' + return f"JSON_EXISTS(metadata, '$.{key}?(@ {oper} {value})')" + + +def _generate_where_clause(db_filter: Union[FilterCondition, FilterGroup]) -> str: + if "key" in db_filter: # Identify as FilterCondition + return _generate_condition(cast(FilterCondition, db_filter)) + + if "_and" in db_filter and db_filter["_and"] is not None: + and_conditions = [ + _generate_where_clause(cond) + for cond in db_filter["_and"] + if isinstance(cond, dict) + ] + return "(" + " AND ".join(and_conditions) + ")" + + if "_or" in db_filter and db_filter["_or"] is not None: + or_conditions = [ + _generate_where_clause(cond) + for cond in db_filter["_or"] + if isinstance(cond, dict) + ] + return "(" + " OR ".join(or_conditions) + ")" + + raise ValueError(f"Invalid filter structure: {db_filter}") + + def _get_connection(client: Any) -> Connection | None: # check if ConnectionPool exists connection_pool_class = getattr(oracledb, "ConnectionPool", None) @@ -95,7 +146,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: def _table_exists(connection: Connection, table_name: str) -> bool: try: with connection.cursor() as cursor: - cursor.execute(f"SELECT COUNT(*) FROM {table_name}") + cursor.execute(f"SELECT 1 FROM {table_name} WHERE ROWNUM < 1") return True except oracledb.DatabaseError as ex: err_obj = ex.args @@ -446,6 +497,7 @@ class OracleVS(VectorStore): vectors = OracleVS(connection, embeddings, table_name, query) """ + @_handle_exceptions def __init__( self, client: Any, @@ -494,39 +546,30 @@ def __init__( if _compare_version(oracledb.__version__, "2.1.0"): self.insert_mode = "clob" - try: - """Initialize with oracledb client.""" - self.client = client - """Initialize with necessary components.""" - if not isinstance(embedding_function, Embeddings): - logger.warning( - "`embedding_function` is expected to be an Embeddings " - "object, support " - "for passing in a function will soon be removed." - ) - self.embedding_function = embedding_function - self.query = query - embedding_dim = self.get_embedding_dimension() - - self.table_name = table_name - self.distance_strategy = distance_strategy - self.params = params - _create_table(connection, table_name, embedding_dim) - except oracledb.DatabaseError as db_err: - logger.exception(f"Database error occurred while create table: {db_err}") - raise RuntimeError( - "Failed to create table due to a database error." - ) from db_err - except ValueError as val_err: - logger.exception(f"Validation error: {val_err}") - raise RuntimeError( - "Failed to create table due to a validation error." - ) from val_err - except Exception as ex: - logger.exception("An unexpected error occurred while creating the index.") - raise RuntimeError( - "Failed to create table due to an unexpected error." - ) from ex + self.json_insert_mode = "clob" + if ( + hasattr(connection, "thin") and connection.thin + ) or oracledb.clientversion()[0] >= 21: + self.json_insert_mode = "json" + self.json_type = oracledb.DB_TYPE_JSON + + """Initialize with oracledb client.""" + self.client = client + """Initialize with necessary components.""" + if not isinstance(embedding_function, Embeddings): + logger.warning( + "`embedding_function` is expected to be an Embeddings " + "object, support " + "for passing in a function will soon be removed." + ) + self.embedding_function = embedding_function + self.query = query + embedding_dim = self.get_embedding_dimension() + + self.table_name = table_name + self.distance_strategy = distance_strategy + self.params = params + _create_table(connection, table_name, embedding_dim) @property def embeddings(self) -> Optional[Embeddings]: @@ -616,14 +659,28 @@ def add_texts( docs: List[Tuple[Any, Any, Any, Any]] if self.insert_mode == "clob": docs = [ - (id_, json.dumps(embedding), json.dumps(metadata), text) + ( + id_, + json.dumps(embedding), + json.dumps(metadata) + if self.json_insert_mode != "json" + else metadata, + text, + ) for id_, embedding, metadata, text in zip( processed_ids, embeddings, metadatas, texts ) ] else: docs = [ - (id_, array.array("f", embedding), json.dumps(metadata), text) + ( + id_, + array.array("f", embedding), + json.dumps(metadata) + if self.json_insert_mode != "json" + else metadata, + text, + ) for id_, embedding, metadata, text in zip( processed_ids, embeddings, metadatas, texts ) @@ -633,6 +690,8 @@ def add_texts( if connection is None: raise ValueError("Failed to acquire a connection.") with connection.cursor() as cursor: + if self.json_insert_mode == "json": + cursor.setinputsizes(None, None, self.json_type, None) cursor.executemany( f"INSERT INTO {self.table_name} (id, embedding, metadata, " f"text) VALUES (:1, :2, :3, :4)", @@ -719,7 +778,9 @@ def similarity_search_by_vector_with_relevance_scores( else: embedding_arr = array.array("f", embedding) - query = f""" + db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) + if db_filter is None: + query = f""" SELECT id, text, metadata, @@ -728,7 +789,20 @@ def similarity_search_by_vector_with_relevance_scores( FROM {self.table_name} ORDER BY distance FETCH APPROX FIRST {k} ROWS ONLY - """ + """ + else: + where_clause = _generate_where_clause(db_filter) + query = f""" + SELECT id, + text, + metadata, + vector_distance(embedding, :embedding, + {_get_distance_function(self.distance_strategy)}) as distance + FROM {self.table_name} + WHERE {where_clause} + ORDER BY distance + FETCH APPROX FIRST {k} ROWS ONLY + """ # Execute the query connection = _get_connection(self.client) @@ -740,7 +814,7 @@ def similarity_search_by_vector_with_relevance_scores( # Filter results if filter is provided for result in results: - metadata = dict(result[2]) if isinstance(result[2], dict) else {} + metadata = metadata = result[2] or {} # Apply filtering based on the 'filter' dictionary if filter: @@ -785,19 +859,32 @@ def similarity_search_by_vector_returning_embeddings( documents = [] - query = f""" + db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) + if db_filter is None: + query = f""" SELECT id, text, metadata, - vector_distance(embedding, :embedding, { - _get_distance_function(self.distance_strategy) - }) as distance, + vector_distance(embedding, :embedding, {_get_distance_function( + self.distance_strategy)}) as distance, embedding FROM {self.table_name} ORDER BY distance FETCH APPROX FIRST {k} ROWS ONLY - """ - + """ + else: + where_clause = _generate_where_clause(db_filter) + query = f""" + SELECT id, + text, + metadata, + vector_distance(embedding, :embedding, {_get_distance_function( + self.distance_strategy)}) as distance, embedding + FROM {self.table_name} + WHERE {where_clause} + ORDER BY distance + FETCH APPROX FIRST {k} ROWS ONLY + """ # Execute the query connection = _get_connection(self.client) if connection is None: @@ -808,7 +895,7 @@ def similarity_search_by_vector_returning_embeddings( for result in results: page_content_str = self._get_clob_value(result[1]) - metadata = result[2] if isinstance(result[2], dict) else {} + metadata = result[2] or {} # Apply filter if provided and matches; otherwise, add all # documents diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py index 19ead0f..47834ea 100644 --- a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -954,3 +954,127 @@ def test_perform_search_test() -> None: drop_table_purge(connection, "TB13") drop_table_purge(connection, "TB14") drop_table_purge(connection, "TB15") + + +################################## +##### perform_filter_search ###### +################################## +def test_db_filter_test() -> None: + try: + import oracledb + except ImportError: + return + + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + vs_1 = OracleVS(connection, model1, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE) + vs_2 = OracleVS(connection, model1, "TB11", DistanceStrategy.DOT_PRODUCT) + vs_3 = OracleVS(connection, model1, "TB12", DistanceStrategy.COSINE) + vs_4 = OracleVS(connection, model1, "TB13", DistanceStrategy.EUCLIDEAN_DISTANCE) + vs_5 = OracleVS(connection, model1, "TB14", DistanceStrategy.DOT_PRODUCT) + vs_6 = OracleVS(connection, model1, "TB15", DistanceStrategy.COSINE) + + # vector store lists: + vs_list = [vs_1, vs_2, vs_3, vs_4, vs_5, vs_6] + + for i, vs in enumerate(vs_list): + # insert data + texts = ["Strawberry", "Banana", "Blueberry", "Grape", "Watermelon"] + metadatas = [ + {"id": "st", "order": 1}, + {"id": "ba", "order": 2}, + {"id": "bl", "order": 3}, + {"id": "gr", "order": 4}, + {"id": "wa", "order": 5}, + ] + + vs.add_texts(texts, metadatas) + + # create index + if i == 1 or i == 2 or i == 3: + create_index(connection, vs, {"idx_type": "HNSW", "idx_name": f"IDX1{i}"}) + else: + create_index(connection, vs, {"idx_type": "IVF", "idx_name": f"IDX1{i}"}) + + # perform search + query = "Strawberry" + + filter = {"id": ["bl"]} + db_filter = {"key": "id", "oper": "EQ", "value": "bl"} # FilterCondition + + # similarity_search without filter + result = vs.similarity_search(query, 1) + assert result[0].metadata["id"] == "st" + + # similarity_search with filter + result = vs.similarity_search(query, 1, filter=filter) + assert len(result) == 0 + + # similarity_search with db_filter + result = vs.similarity_search(query, 1, db_filter=db_filter) + assert result[0].metadata["id"] == "bl" + + # similarity_search with filter and db_filter + result = vs.similarity_search(query, 1, filter=filter, db_filter=db_filter) + assert result[0].metadata["id"] == "bl" + + # nested db filter + db_filter_nested = { + "_or": [ + {"key": "id", "oper": "EQ", "value": "ba"}, # FilterCondition + { + "_and": [ # FilterGroup + {"key": "order", "oper": "LTE", "value": 4}, + {"key": "id", "oper": "EQ", "value": "st"}, + ] + }, + ] + } + + # similarity_search with db_filter + result = vs.similarity_search(query, 1, db_filter=db_filter_nested) + assert result[0].metadata["id"] == "st" + + exception_occurred = False + try: + db_filter_exc = { + "_xor": [ # Incorrect operation _xor + {"key": "id", "oper": "EQ", "value": "ba"}, + {"key": "order", "oper": "LTE", "value": 4}, + ] + } + result = vs.similarity_search(query, 1, db_filter=db_filter_exc) + except ValueError: + exception_occurred = True + + assert exception_occurred + + exception_occurred = False + try: + db_filter_exc = { + "_or": [ + { + "key": "id", + "oper": "XEQ", + "value": "ba", + }, # Incorrect operation XEQ + {"key": "order", "oper": "LTE", "value": 4}, + ] + } + result = vs.similarity_search(query, 1, db_filter=db_filter_exc) + except ValueError: + exception_occurred = True + + assert exception_occurred + + drop_table_purge(connection, "TB10") + drop_table_purge(connection, "TB11") + drop_table_purge(connection, "TB12") + drop_table_purge(connection, "TB13") + drop_table_purge(connection, "TB14") + drop_table_purge(connection, "TB15") From 3a695e767e4cc1423b55ab67c20fbb5756590389 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 10 Jul 2025 15:57:25 +0000 Subject: [PATCH 03/12] Add async support --- .../vectorstores/oraclevs.py | 1083 +++++++++++-- .../vectorstores/test_oraclevs.py | 1427 +++++++++++++++-- 2 files changed, 2259 insertions(+), 251 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index 9503ce8..7d4f231 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -6,10 +6,12 @@ import array import functools import hashlib +import inspect import json import logging import os import uuid +from collections.abc import Awaitable from typing import ( TYPE_CHECKING, Any, @@ -29,7 +31,7 @@ from numpy.typing import NDArray if TYPE_CHECKING: - from oracledb import Connection + from oracledb import AsyncConnection, Connection import numpy as np import oracledb @@ -120,6 +122,23 @@ def _get_connection(client: Any) -> Connection | None: ) +async def _aget_connection(client: Any) -> AsyncConnection | None: + # check if ConnectionPool exists + connection_pool_class = getattr(oracledb, "AsyncConnectionPool", None) + + if isinstance(client, oracledb.AsyncConnection): + return client + elif connection_pool_class and isinstance(client, connection_pool_class): + return await client.acquire() + else: + valid_types = "oracledb.AsyncConnection" + if connection_pool_class: + valid_types += " or oracledb.AsyncConnectionPool" + raise TypeError( + f"Expected client of type {valid_types}, got {type(client).__name__}" + ) + + def _handle_exceptions(func: T) -> T: @functools.wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: @@ -146,7 +165,19 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: def _table_exists(connection: Connection, table_name: str) -> bool: try: with connection.cursor() as cursor: - cursor.execute(f"SELECT 1 FROM {table_name} WHERE ROWNUM < 1") + cursor.execute(f"select 1 from {table_name} where ROWNUM < 1") + return True + except oracledb.DatabaseError as ex: + err_obj = ex.args + if err_obj[0].code == 942: + return False + raise + + +async def _atable_exists(connection: AsyncConnection, table_name: str) -> bool: + try: + with connection.cursor() as cursor: + await cursor.execute(f"select 1 from {table_name} where ROWNUM < 1") return True except oracledb.DatabaseError as ex: err_obj = ex.args @@ -189,6 +220,24 @@ def _index_exists(connection: Connection, index_name: str) -> bool: return result is not None +@_handle_exceptions +async def _aindex_exists(connection: AsyncConnection, index_name: str) -> bool: + # Check if the index exists + query = """ + SELECT index_name + FROM all_indexes + WHERE upper(index_name) = upper(:idx_name) + """ + + with connection.cursor() as cursor: + # Execute the query + await cursor.execute(query, idx_name=index_name.upper()) + result = await cursor.fetchone() + + # Check if the index exists + return result is not None + + def _get_distance_function(distance_strategy: DistanceStrategy) -> str: # Dictionary to map distance strategies to their corresponding function # names @@ -211,14 +260,19 @@ def _get_index_name(base_name: str) -> str: return f"{base_name}_{unique_id}" -@_handle_exceptions -def _create_table(connection: Connection, table_name: str, embedding_dim: int) -> None: +def _get_table_dict(embedding_dim: int) -> Dict: cols_dict = { "id": "RAW(16) DEFAULT SYS_GUID() PRIMARY KEY", "text": "CLOB", "metadata": "JSON", "embedding": f"vector({embedding_dim}, FLOAT32)", } + return cols_dict + + +@_handle_exceptions +def _create_table(connection: Connection, table_name: str, embedding_dim: int) -> None: + cols_dict = _get_table_dict(embedding_dim) if not _table_exists(connection, table_name): with connection.cursor() as cursor: @@ -232,6 +286,24 @@ def _create_table(connection: Connection, table_name: str, embedding_dim: int) - logger.info("Table already exists...") +@_handle_exceptions +async def _acreate_table( + connection: AsyncConnection, table_name: str, embedding_dim: int +) -> None: + cols_dict = _get_table_dict(embedding_dim) + + if not await _atable_exists(connection, table_name): + with connection.cursor() as cursor: + ddl_body = ", ".join( + f"{col_name} {col_type}" for col_name, col_type in cols_dict.items() + ) + ddl = f"CREATE TABLE {table_name} ({ddl_body})" + await cursor.execute(ddl) + logger.info("Table created successfully...") + else: + logger.info("Table already exists...") + + @_handle_exceptions def create_index( client: Any, @@ -270,13 +342,11 @@ def create_index( return -@_handle_exceptions -def _create_hnsw_index( - connection: Connection, +def _get_hnsw_index_ddl( table_name: str, distance_strategy: DistanceStrategy, params: Optional[dict[str, Any]] = None, -) -> None: +) -> Tuple[str, str]: defaults = { "idx_name": "HNSW", "idx_type": "HNSW", @@ -345,8 +415,20 @@ def _create_hnsw_index( # Format the SQL with values from the params dictionary ddl = ddl_assembly.format(**config) + return idx_name, ddl + + +@_handle_exceptions +def _create_hnsw_index( + connection: Connection, + table_name: str, + distance_strategy: DistanceStrategy, + params: Optional[dict[str, Any]] = None, +) -> None: + idx_name, ddl = _get_hnsw_index_ddl(table_name, distance_strategy, params) + # Check if the index exists - if not _index_exists(connection, config["idx_name"]): + if not _index_exists(connection, idx_name): with connection.cursor() as cursor: cursor.execute(ddl) logger.info("Index created successfully...") @@ -354,13 +436,11 @@ def _create_hnsw_index( logger.info("Index already exists...") -@_handle_exceptions -def _create_ivf_index( - connection: Connection, +def _get_ivf_index_ddl( table_name: str, distance_strategy: DistanceStrategy, params: Optional[dict[str, Any]] = None, -) -> None: +) -> Tuple[str, str]: # Default configuration defaults = { "idx_name": "IVF", @@ -417,8 +497,20 @@ def _create_ivf_index( # Format the SQL with values from the params dictionary ddl = ddl_assembly.format(**config) + return idx_name, ddl + + +@_handle_exceptions +def _create_ivf_index( + connection: Connection, + table_name: str, + distance_strategy: DistanceStrategy, + params: Optional[dict[str, Any]] = None, +) -> None: + idx_name, ddl = _get_ivf_index_ddl(table_name, distance_strategy, params) + # Check if the index exists - if not _index_exists(connection, config["idx_name"]): + if not _index_exists(connection, idx_name): with connection.cursor() as cursor: cursor.execute(ddl) logger.info("Index created successfully...") @@ -426,6 +518,84 @@ def _create_ivf_index( logger.info("Index already exists...") +@_handle_exceptions +async def acreate_index( + client: Any, + vector_store: OracleVS, + params: Optional[dict[str, Any]] = None, +) -> None: + async def context(connection: Any) -> None: + if params: + if params["idx_type"] == "HNSW": + await _acreate_hnsw_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + elif params["idx_type"] == "IVF": + await _acreate_ivf_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + else: + await _acreate_hnsw_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + + else: + await _acreate_hnsw_index( + connection, + vector_store.table_name, + vector_store.distance_strategy, + params, + ) + + await _handle_context(client, context) + return + + +@_handle_exceptions +async def _acreate_hnsw_index( + connection: AsyncConnection, + table_name: str, + distance_strategy: DistanceStrategy, + params: Optional[dict[str, Any]] = None, +) -> None: + idx_name, ddl = _get_hnsw_index_ddl(table_name, distance_strategy, params) + + # Check if the index exists + if not await _aindex_exists(connection, idx_name): + with connection.cursor() as cursor: + await cursor.execute(ddl) + logger.info("Index created successfully...") + else: + logger.info("Index already exists...") + + +@_handle_exceptions +async def _acreate_ivf_index( + connection: AsyncConnection, + table_name: str, + distance_strategy: DistanceStrategy, + params: Optional[dict[str, Any]] = None, +) -> None: + idx_name, ddl = _get_ivf_index_ddl(table_name, distance_strategy, params) + + # Check if the index exists + if not await _aindex_exists(connection, idx_name): + with connection.cursor() as cursor: + await cursor.execute(ddl) + logger.info("Index created successfully...") + else: + logger.info("Index already exists...") + + @_handle_exceptions def drop_table_purge(client: Any, table_name: str) -> None: """Drop a table and purge it from the database. @@ -450,6 +620,31 @@ def drop_table_purge(client: Any, table_name: str) -> None: return +@_handle_exceptions +async def adrop_table_purge(client: Any, table_name: str) -> None: + """Drop a table and purge it from the database. + + Args: + client: The OracleDB connection object. + table_name: The name of the table to drop. + + Raises: + RuntimeError: If an error occurs while dropping the table. + """ + + async def context(connection: Any) -> None: + if await _atable_exists(connection, table_name): + with connection.cursor() as cursor: + ddl = f"DROP TABLE {table_name} PURGE" + await cursor.execute(ddl) + logger.info("Table dropped successfully...") + else: + logger.info("Table not found...") + + await _handle_context(client, context) + return + + @_handle_exceptions def drop_index_if_exists(client: Any, index_name: str) -> None: """Drop an index if it exists. @@ -474,6 +669,160 @@ def drop_index_if_exists(client: Any, index_name: str) -> None: return +@_handle_exceptions +async def adrop_index_if_exists(client: Any, index_name: str) -> None: + """Drop an index if it exists. + + Args: + client: The OracleDB connection object. + index_name: The name of the index to drop. + + Raises: + RuntimeError: If an error occurs while dropping the index. + """ + + async def context(connection: Any) -> None: + if await _aindex_exists(connection, index_name): + drop_query = f"DROP INDEX {index_name}" + with connection.cursor() as cursor: + await cursor.execute(drop_query) + logger.info(f"Index {index_name} has been dropped.") + else: + logger.exception(f"Index {index_name} does not exist.") + + await _handle_context(client, context) + return + + +def get_processed_ids( + texts: Iterable[str], + metadatas: Optional[List[Dict[Any, Any]]] = None, + ids: Optional[List[str]] = None, +) -> List[str]: + if ids: + # If ids are provided, hash them to maintain consistency + processed_ids = [ + hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids + ] + elif metadatas and all("id" in metadata for metadata in metadatas): + # If no ids are provided but metadatas with ids are, generate + # ids from metadatas + processed_ids = [ + hashlib.sha256(metadata["id"].encode()).hexdigest()[:16].upper() + for metadata in metadatas + ] + else: + # Generate new ids if none are provided + generated_ids = [ + str(uuid.uuid4()) for _ in texts + ] # uuid4 is more standard for random UUIDs + processed_ids = [ + hashlib.sha256(_id.encode()).hexdigest()[:16].upper() + for _id in generated_ids + ] + + return processed_ids + + +def _get_delete_ddl( + table_name: str, ids: Optional[List[str]] = None +) -> Tuple[str, Dict]: + if ids is None: + raise ValueError("No ids provided to delete.") + + # Compute SHA-256 hashes of the ids and truncate them + hashed_ids = [hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids] + + # Constructing the SQL statement with individual placeholders + placeholders = ", ".join([":id" + str(i + 1) for i in range(len(hashed_ids))]) + + ddl = f"DELETE FROM {table_name} WHERE id IN ({placeholders})" + + # Preparing bind variables + bind_vars = {f"id{i}": hashed_id for i, hashed_id in enumerate(hashed_ids, start=1)} + + return ddl, bind_vars + + +def mmr_from_docs_embeddings( + docs_scores_embeddings: List[Tuple[Document, float, NDArray[np.float32]]], + embedding: List[float], + k: int = 4, + lambda_mult: float = 0.5, +) -> List[Tuple[Document, float]]: + # If you need to split documents and scores for processing (e.g., + # for MMR calculation) + documents, scores, embeddings = ( + zip(*docs_scores_embeddings) if docs_scores_embeddings else ([], [], []) + ) + + # Assume maximal_marginal_relevance method accepts embeddings and + # scores, and returns indices of selected docs + mmr_selected_indices = maximal_marginal_relevance( + np.array(embedding, dtype=np.float32), + list(embeddings), + k=k, + lambda_mult=lambda_mult, + ) + + # Filter documents based on MMR-selected indices and map scores + mmr_selected_documents_with_scores = [ + (documents[i], scores[i]) for i in mmr_selected_indices + ] + + return mmr_selected_documents_with_scores + + +def _get_similarity_search_query( + table_name: str, + distance_strategy: DistanceStrategy, + k: int, + db_filter: Optional[FilterGroup], + return_embeddings: bool = False, +) -> str: + where_clause = "" + if db_filter: + where_clause = _generate_where_clause(db_filter) + + query = f""" + SELECT id, + text, + metadata, + vector_distance(embedding, :embedding, + {_get_distance_function(distance_strategy)}) as distance + {",embedding" if return_embeddings else ""} + FROM {table_name} + {f"WHERE {where_clause}" if db_filter else ""} + ORDER BY distance + FETCH APPROX FIRST {k} ROWS ONLY + """ + + return query + + +@_handle_exceptions +async def _handle_context( + client: Any, + context: Callable[ + [Any], + Awaitable[Any], + ], +) -> Any: + """ + AsyncConnectionPool connections are not released automatically, + therefore needs to be used in a context manager + """ + connection = await _aget_connection(client) + if connection is None: + raise ValueError("Failed to acquire a connection.") + + if connection._pool: + async with connection as conn: + return await context(conn) + else: + return await context(connection) + + class OracleVS(VectorStore): """`OracleVS` vector store. @@ -510,11 +859,80 @@ def __init__( query: Optional[str] = "What is a Oracle database", params: Optional[Dict[str, Any]] = None, ): - self.insert_mode = "array" + """ + Initialize the OracleVS store. + For an async version, use OracleVS.acreate() instead. + """ connection = _get_connection(client) if connection is None: raise ValueError("Failed to acquire a connection.") + self._initialize( + connection, + client, + embedding_function, + table_name, + distance_strategy, + query, + params, + ) + + embedding_dim = self.get_embedding_dimension() + _create_table(connection, table_name, embedding_dim) + + @classmethod + @_handle_exceptions + async def acreate( + cls, + client: Any, + embedding_function: Union[ + Callable[[str], List[float]], + Embeddings, + ], + table_name: str, + distance_strategy: DistanceStrategy = DistanceStrategy.EUCLIDEAN_DISTANCE, + query: Optional[str] = "What is a Oracle database", + params: Optional[Dict[str, Any]] = None, + ) -> OracleVS: + """ + Initialize the OracleVS store with async connection. + """ + + self = cls.__new__(cls) + + async def context(connection: Any) -> None: + self._initialize( + connection, + client, + embedding_function, + table_name, + distance_strategy, + query, + params, + ) + + embedding_dim = await self.aget_embedding_dimension() + await _acreate_table(connection, table_name, embedding_dim) + + await _handle_context(client, context) + + return self + + def _initialize( + self, + connection: Any, + client: Any, + embedding_function: Union[ + Callable[[str], List[float]], + Embeddings, + ], + table_name: str, + distance_strategy: DistanceStrategy = DistanceStrategy.EUCLIDEAN_DISTANCE, + query: Optional[str] = "What is a Oracle database", + params: Optional[Dict[str, Any]] = None, + ) -> None: + self.insert_mode = "array" + if hasattr(connection, "thin") and connection.thin: if oracledb.__version__ == "2.1.0": raise Exception( @@ -564,12 +982,9 @@ def __init__( ) self.embedding_function = embedding_function self.query = query - embedding_dim = self.get_embedding_dimension() - self.table_name = table_name self.distance_strategy = distance_strategy self.params = params - _create_table(connection, table_name, embedding_dim) @property def embeddings(self) -> Optional[Embeddings]: @@ -596,6 +1011,15 @@ def get_embedding_dimension(self) -> int: # Get the first (and only) embedding's dimension return len(embedded_document[0]) + async def aget_embedding_dimension(self) -> int: + # Embed the single document by wrapping it in a list + embedded_document = await self._aembed_documents( + [self.query if self.query is not None else ""] + ) + + # Get the first (and only) embedding's dimension + return len(embedded_document[0]) + def _embed_documents(self, texts: List[str]) -> List[List[float]]: if isinstance(self.embedding_function, Embeddings): return self.embedding_function.embed_documents(texts) @@ -606,9 +1030,29 @@ def _embed_documents(self, texts: List[str]) -> List[List[float]]: "The embedding_function is neither Embeddings nor callable." ) - def _embed_query(self, text: str) -> List[float]: + async def _aembed_documents(self, texts: List[str]) -> List[List[float]]: if isinstance(self.embedding_function, Embeddings): - return self.embedding_function.embed_query(text) + return await self.embedding_function.aembed_documents(texts) + elif inspect.isawaitable(self.embedding_function): + return [await self.embedding_function(text) for text in texts] + elif callable(self.embedding_function): + return [self.embedding_function(text) for text in texts] + else: + raise TypeError( + "The embedding_function is neither Embeddings nor callable." + ) + + def _embed_query(self, text: str) -> List[float]: + if isinstance(self.embedding_function, Embeddings): + return self.embedding_function.embed_query(text) + else: + return self.embedding_function(text) + + async def _aembed_query(self, text: str) -> List[float]: + if isinstance(self.embedding_function, Embeddings): + return await self.embedding_function.aembed_query(text) + elif inspect.isawaitable(self.embedding_function): + return await self.embedding_function(text) else: return self.embedding_function(text) @@ -630,27 +1074,7 @@ def add_texts( """ texts = list(texts) - if ids: - # If ids are provided, hash them to maintain consistency - processed_ids = [ - hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids - ] - elif metadatas and all("id" in metadata for metadata in metadatas): - # If no ids are provided but metadatas with ids are, generate - # ids from metadatas - processed_ids = [ - hashlib.sha256(metadata["id"].encode()).hexdigest()[:16].upper() - for metadata in metadatas - ] - else: - # Generate new ids if none are provided - generated_ids = [ - str(uuid.uuid4()) for _ in texts - ] # uuid4 is more standard for random UUIDs - processed_ids = [ - hashlib.sha256(_id.encode()).hexdigest()[:16].upper() - for _id in generated_ids - ] + processed_ids = get_processed_ids(texts, metadatas, ids) embeddings = self._embed_documents(texts) if not metadatas: @@ -700,6 +1124,78 @@ def add_texts( connection.commit() return processed_ids + @_handle_exceptions + async def aadd_texts( + self, + texts: Iterable[str], + metadatas: Optional[list[dict]] = None, + *, + ids: Optional[list[str]] = None, + **kwargs: Any, + ) -> list[str]: + """Add more texts to the vectorstore index, async. + Args: + texts: Iterable of strings to add to the vectorstore. + metadatas: Optional list of metadatas associated with the texts. + ids: Optional list of ids for the texts that are being added to + the vector store. + kwargs: vectorstore specific parameters + """ + + texts = list(texts) + processed_ids = get_processed_ids(texts, metadatas, ids) + + embeddings = await self._aembed_documents(texts) + if not metadatas: + metadatas = [{} for _ in texts] + + docs: List[Tuple[Any, Any, Any, Any]] + if self.insert_mode == "clob": + docs = [ + ( + id_, + json.dumps(embedding), + json.dumps(metadata) + if self.json_insert_mode != "json" + else metadata, + text, + ) + for id_, embedding, metadata, text in zip( + processed_ids, embeddings, metadatas, texts + ) + ] + else: + docs = [ + ( + id_, + array.array("f", embedding), + json.dumps(metadata) + if self.json_insert_mode != "json" + else metadata, + text, + ) + for id_, embedding, metadata, text in zip( + processed_ids, embeddings, metadatas, texts + ) + ] + + async def context(connection: Any) -> None: + if connection is None: + raise ValueError("Failed to acquire a connection.") + with connection.cursor() as cursor: + if self.json_insert_mode == "json": + cursor.setinputsizes(None, None, self.json_type, None) + await cursor.executemany( + f"INSERT INTO {self.table_name} (id, embedding, metadata, " + f"text) VALUES (:1, :2, :3, :4)", + docs, + ) + await connection.commit() + + await _handle_context(self.client, context) + + return processed_ids + def similarity_search( self, query: str, @@ -716,6 +1212,22 @@ def similarity_search( ) return documents + async def asimilarity_search( + self, + query: str, + k: int = 4, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + """Return docs most similar to query.""" + embedding: List[float] = [] + if isinstance(self.embedding_function, Embeddings): + embedding = await self.embedding_function.aembed_query(query) + documents = await self.asimilarity_search_by_vector( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return documents + def similarity_search_by_vector( self, embedding: List[float], @@ -728,6 +1240,18 @@ def similarity_search_by_vector( ) return [doc for doc, _ in docs_and_scores] + async def asimilarity_search_by_vector( + self, + embedding: List[float], + k: int = 4, + filter: Optional[dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + docs_and_scores = await self.asimilarity_search_by_vector_with_relevance_scores( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return [doc for doc, _ in docs_and_scores] + def similarity_search_with_score( self, query: str, @@ -744,6 +1268,22 @@ def similarity_search_with_score( ) return docs_and_scores + async def asimilarity_search_with_score( + self, + query: str, + k: int = 4, + filter: Optional[dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + """Return docs most similar to query.""" + embedding: List[float] = [] + if isinstance(self.embedding_function, Embeddings): + embedding = await self.embedding_function.aembed_query(query) + docs_and_scores = await self.asimilarity_search_by_vector_with_relevance_scores( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return docs_and_scores + @_handle_exceptions def _get_clob_value(self, result: Any) -> str: clob_value = "" @@ -762,6 +1302,24 @@ def _get_clob_value(self, result: Any) -> str: raise Exception("Unexpected type:", type(result)) return clob_value + @_handle_exceptions + async def _aget_clob_value(self, result: Any) -> str: + clob_value = "" + if result: + if isinstance(result, oracledb.AsyncLOB): + raw_data = await result.read() + if isinstance(raw_data, bytes): + clob_value = raw_data.decode( + "utf-8" + ) # Specify the correct encoding + else: + clob_value = raw_data + elif isinstance(result, str): + clob_value = result + else: + raise Exception("Unexpected type:", type(result)) + return clob_value + @_handle_exceptions def similarity_search_by_vector_with_relevance_scores( self, @@ -779,30 +1337,13 @@ def similarity_search_by_vector_with_relevance_scores( embedding_arr = array.array("f", embedding) db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) - if db_filter is None: - query = f""" - SELECT id, - text, - metadata, - vector_distance(embedding, :embedding, - {_get_distance_function(self.distance_strategy)}) as distance - FROM {self.table_name} - ORDER BY distance - FETCH APPROX FIRST {k} ROWS ONLY - """ - else: - where_clause = _generate_where_clause(db_filter) - query = f""" - SELECT id, - text, - metadata, - vector_distance(embedding, :embedding, - {_get_distance_function(self.distance_strategy)}) as distance - FROM {self.table_name} - WHERE {where_clause} - ORDER BY distance - FETCH APPROX FIRST {k} ROWS ONLY - """ + query = _get_similarity_search_query( + self.table_name, + self.distance_strategy, + k, + db_filter, + return_embeddings=False, + ) # Execute the query connection = _get_connection(self.client) @@ -814,34 +1355,78 @@ def similarity_search_by_vector_with_relevance_scores( # Filter results if filter is provided for result in results: - metadata = metadata = result[2] or {} + metadata = result[2] or {} + + doc = Document( + page_content=( + self._get_clob_value(result[1]) if result[1] is not None else "" + ), + metadata=metadata, + ) + distance = result[3] # Apply filtering based on the 'filter' dictionary - if filter: - if all(metadata.get(key) in value for key, value in filter.items()): - doc = Document( - page_content=( - self._get_clob_value(result[1]) - if result[1] is not None - else "" - ), - metadata=metadata, - ) - distance = result[3] - docs_and_scores.append((doc, distance)) - else: + if not filter or all( + metadata.get(key) in value for key, value in filter.items() + ): + docs_and_scores.append((doc, distance)) + + return docs_and_scores + + @_handle_exceptions + async def asimilarity_search_by_vector_with_relevance_scores( + self, + embedding: List[float], + k: int = 4, + filter: Optional[dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float]]: + docs_and_scores = [] + + embedding_arr: Any + if self.insert_mode == "clob": + embedding_arr = json.dumps(embedding) + else: + embedding_arr = array.array("f", embedding) + + db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) + query = _get_similarity_search_query( + self.table_name, + self.distance_strategy, + k, + db_filter, + return_embeddings=False, + ) + + async def context(connection: Any) -> List: + # Execute the query + with connection.cursor() as cursor: + await cursor.execute(query, embedding=embedding_arr) + results = await cursor.fetchall() + + # Filter results if filter is provided + for result in results: + metadata = result[2] or {} + doc = Document( page_content=( - self._get_clob_value(result[1]) + await self._aget_clob_value(result[1]) if result[1] is not None else "" ), metadata=metadata, ) distance = result[3] - docs_and_scores.append((doc, distance)) - return docs_and_scores + # Apply filtering based on the 'filter' dictionary + if not filter or all( + metadata.get(key) in value for key, value in filter.items() + ): + docs_and_scores.append((doc, distance)) + + return docs_and_scores + + return await _handle_context(self.client, context) @_handle_exceptions def similarity_search_by_vector_returning_embeddings( @@ -860,31 +1445,14 @@ def similarity_search_by_vector_returning_embeddings( documents = [] db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) - if db_filter is None: - query = f""" - SELECT id, - text, - metadata, - vector_distance(embedding, :embedding, {_get_distance_function( - self.distance_strategy)}) as distance, - embedding - FROM {self.table_name} - ORDER BY distance - FETCH APPROX FIRST {k} ROWS ONLY - """ - else: - where_clause = _generate_where_clause(db_filter) - query = f""" - SELECT id, - text, - metadata, - vector_distance(embedding, :embedding, {_get_distance_function( - self.distance_strategy)}) as distance, embedding - FROM {self.table_name} - WHERE {where_clause} - ORDER BY distance - FETCH APPROX FIRST {k} ROWS ONLY - """ + query = _get_similarity_search_query( + self.table_name, + self.distance_strategy, + k, + db_filter, + return_embeddings=True, + ) + # Execute the query connection = _get_connection(self.client) if connection is None: @@ -916,8 +1484,68 @@ def similarity_search_by_vector_returning_embeddings( ) documents.append((document, distance, current_embedding)) + return documents + @_handle_exceptions + async def asimilarity_search_by_vector_returning_embeddings( + self, + embedding: List[float], + k: int, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Tuple[Document, float, NDArray[np.float32]]]: + embedding_arr: Any + if self.insert_mode == "clob": + embedding_arr = json.dumps(embedding) + else: + embedding_arr = array.array("f", embedding) + + documents = [] + + db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) + query = _get_similarity_search_query( + self.table_name, + self.distance_strategy, + k, + db_filter, + return_embeddings=True, + ) + + async def context(connection: Any) -> List: + # Execute the query + with connection.cursor() as cursor: + await cursor.execute(query, embedding=embedding_arr) + results = await cursor.fetchall() + + for result in results: + page_content_str = await self._aget_clob_value(result[1]) + metadata = result[2] or {} + + # Apply filter if provided and matches; otherwise, add all + # documents + if not filter or all( + metadata.get(key) in value for key, value in filter.items() + ): + document = Document( + page_content=page_content_str, metadata=metadata + ) + distance = result[3] + + # Assuming result[4] is already in the correct format; + # adjust if necessary + current_embedding = ( + np.array(result[4], dtype=np.float32) + if result[4] + else np.empty(0, dtype=np.float32) + ) + + documents.append((document, distance, current_embedding)) + + return documents + + return await _handle_context(self.client, context) + @_handle_exceptions def max_marginal_relevance_search_with_score_by_vector( self, @@ -959,26 +1587,58 @@ def max_marginal_relevance_search_with_score_by_vector( embedding, fetch_k, filter=filter ) # Assuming documents_with_scores is a list of tuples (Document, score) - - # If you need to split documents and scores for processing (e.g., - # for MMR calculation) - documents, scores, embeddings = ( - zip(*docs_scores_embeddings) if docs_scores_embeddings else ([], [], []) + mmr_selected_documents_with_scores = mmr_from_docs_embeddings( + docs_scores_embeddings, embedding, k, lambda_mult ) - # Assume maximal_marginal_relevance method accepts embeddings and - # scores, and returns indices of selected docs - mmr_selected_indices = maximal_marginal_relevance( - np.array(embedding, dtype=np.float32), - list(embeddings), - k=k, - lambda_mult=lambda_mult, - ) + return mmr_selected_documents_with_scores - # Filter documents based on MMR-selected indices and map scores - mmr_selected_documents_with_scores = [ - (documents[i], scores[i]) for i in mmr_selected_indices - ] + @_handle_exceptions + async def amax_marginal_relevance_search_with_score_by_vector( + self, + embedding: List[float], + *, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + filter: Optional[Dict[str, Any]] = None, + ) -> List[Tuple[Document, float]]: + """Return docs and their similarity scores selected using the + maximal marginal + relevance. + + Maximal marginal relevance optimizes for similarity to query AND + diversity + among selected documents. + + Args: + self: An instance of the class + embedding: Embedding to look up documents similar to. + k: Number of Documents to return. Defaults to 4. + fetch_k: Number of Documents to fetch before filtering to + pass to MMR algorithm. + filter: (Optional[Dict[str, str]]): Filter by metadata. Defaults + to None. + lambda_mult: Number between 0 and 1 that determines the degree + of diversity among the results with 0 corresponding + to maximum diversity and 1 to minimum diversity. + Defaults to 0.5. + Returns: + List of Documents and similarity scores selected by maximal + marginal + relevance and score for each. + """ + + # Fetch documents and their scores + docs_scores_embeddings = ( + await self.asimilarity_search_by_vector_returning_embeddings( + embedding, fetch_k, filter=filter + ) + ) + # Assuming documents_with_scores is a list of tuples (Document, score) + mmr_selected_documents_with_scores = mmr_from_docs_embeddings( + docs_scores_embeddings, embedding, k, lambda_mult + ) return mmr_selected_documents_with_scores @@ -1017,6 +1677,43 @@ def max_marginal_relevance_search_by_vector( ) return [doc for doc, _ in docs_and_scores] + @_handle_exceptions + async def amax_marginal_relevance_search_by_vector( + self, + embedding: List[float], + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + """Return docs selected using the maximal marginal relevance. + + Maximal marginal relevance optimizes for similarity to query AND + diversity + among selected documents. + + Args: + self: An instance of the class + embedding: Embedding to look up documents similar to. + k: Number of Documents to return. Defaults to 4. + fetch_k: Number of Documents to fetch to pass to MMR algorithm. + lambda_mult: Number between 0 and 1 that determines the degree + of diversity among the results with 0 corresponding + to maximum diversity and 1 to minimum diversity. + Defaults to 0.5. + filter: Optional[Dict[str, Any]] + **kwargs: Any + Returns: + List of Documents selected by maximal marginal relevance. + """ + docs_and_scores = ( + await self.amax_marginal_relevance_search_with_score_by_vector( + embedding, k=k, fetch_k=fetch_k, lambda_mult=lambda_mult, filter=filter + ) + ) + return [doc for doc, _ in docs_and_scores] + @_handle_exceptions def max_marginal_relevance_search( self, @@ -1061,6 +1758,50 @@ def max_marginal_relevance_search( ) return documents + @_handle_exceptions + async def amax_marginal_relevance_search( + self, + query: str, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + filter: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> List[Document]: + """Return docs selected using the maximal marginal relevance. + + Maximal marginal relevance optimizes for similarity to query AND + diversity + among selected documents. + + Args: + self: An instance of the class + query: Text to look up documents similar to. + k: Number of Documents to return. Defaults to 4. + fetch_k: Number of Documents to fetch to pass to MMR algorithm. + lambda_mult: Number between 0 and 1 that determines the degree + of diversity among the results with 0 corresponding + to maximum diversity and 1 to minimum diversity. + Defaults to 0.5. + filter: Optional[Dict[str, Any]] + **kwargs + Returns: + List of Documents selected by maximal marginal relevance. + + `amax_marginal_relevance_search` requires that `query` returns matched + embeddings alongside the match documents. + """ + embedding = await self._aembed_query(query) + documents = await self.amax_marginal_relevance_search_by_vector( + embedding, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + filter=filter, + **kwargs, + ) + return documents + @_handle_exceptions def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: """Delete by vector IDs. @@ -1070,23 +1811,7 @@ def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: **kwargs """ - if ids is None: - raise ValueError("No ids provided to delete.") - - # Compute SHA-256 hashes of the ids and truncate them - hashed_ids = [ - hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids - ] - - # Constructing the SQL statement with individual placeholders - placeholders = ", ".join([":id" + str(i + 1) for i in range(len(hashed_ids))]) - - ddl = f"DELETE FROM {self.table_name} WHERE id IN ({placeholders})" - - # Preparing bind variables - bind_vars = { - f"id{i}": hashed_id for i, hashed_id in enumerate(hashed_ids, start=1) - } + ddl, bind_vars = _get_delete_ddl(self.table_name, ids) connection = _get_connection(self.client) if connection is None: @@ -1095,15 +1820,32 @@ def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: cursor.execute(ddl, bind_vars) connection.commit() - @classmethod @_handle_exceptions - def from_texts( + async def adelete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: + """Delete by vector IDs. + Args: + self: An instance of the class + ids: List of ids to delete. + **kwargs + """ + + ddl, bind_vars = _get_delete_ddl(self.table_name, ids) + + async def context(connection: Any) -> None: + with connection.cursor() as cursor: + await cursor.execute(ddl, bind_vars) + await connection.commit() + + await _handle_context(self.client, context) + + @classmethod + def _from_texts_helper( cls: Type[OracleVS], texts: Iterable[str], embedding: Embeddings, metadatas: Optional[List[dict]] = None, **kwargs: Any, - ) -> OracleVS: + ) -> Tuple[Any, str, DistanceStrategy, str, Dict]: client: Any = kwargs.get("client", None) if client is None: raise ValueError("client parameter is required...") @@ -1117,11 +1859,30 @@ def from_texts( ) if not isinstance(distance_strategy, DistanceStrategy): raise TypeError( - f"Expected DistanceStrategy got {type(distance_strategy).__name__} " + f"Expected DistanceStrategy got " f"{type(distance_strategy).__name__} " ) query = kwargs.get("query", "What is a Oracle database") + return client, table_name, distance_strategy, query, params + + @classmethod + @_handle_exceptions + def from_texts( + cls: Type[OracleVS], + texts: Iterable[str], + embedding: Embeddings, + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> OracleVS: + ( + client, + table_name, + distance_strategy, + query, + params, + ) = OracleVS._from_texts_helper(texts, embedding, metadatas, **kwargs) + drop_table_purge(client, table_name) vss = cls( @@ -1132,5 +1893,37 @@ def from_texts( query=query, params=params, ) + vss.add_texts(texts=list(texts), metadatas=metadatas) return vss + + @classmethod + @_handle_exceptions + async def afrom_texts( + cls: Type[OracleVS], + texts: Iterable[str], + embedding: Embeddings, + metadatas: Optional[List[dict]] = None, + **kwargs: Any, + ) -> OracleVS: + ( + client, + table_name, + distance_strategy, + query, + params, + ) = OracleVS._from_texts_helper(texts, embedding, metadatas, **kwargs) + + await adrop_table_purge(client, table_name) + + vss = await OracleVS.acreate( + client=client, + embedding_function=embedding, + table_name=table_name, + distance_strategy=distance_strategy, + query=query, + params=params, + ) + + await vss.aadd_texts(texts=list(texts), metadatas=metadatas) + return vss diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py index 47834ea..9e3de66 100644 --- a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -1,17 +1,26 @@ """Test Oracle AI Vector Search functionality.""" # import required modules +import asyncio import sys import threading +import oracledb +import pytest from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores.utils import DistanceStrategy from langchain_oracledb.vectorstores.oraclevs import ( OracleVS, + _acreate_table, + _aindex_exists, + _atable_exists, _create_table, _index_exists, _table_exists, + acreate_index, + adrop_index_if_exists, + adrop_table_purge, create_index, drop_index_if_exists, drop_table_purge, @@ -26,11 +35,6 @@ ####### table_exists ####### ############################ def test_table_exists_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -92,17 +96,69 @@ def test_table_exists_test() -> None: drop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') +@pytest.mark.asyncio +async def test_table_exists_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + # 1. Existing Table:(all capital letters) + # expectation:True + await _atable_exists(connection, "V$TRANSACTION") + + # 2. Existing Table:(all small letters) + # expectation:True + await _atable_exists(connection, "v$transaction") + + # 3. Non-Existing Table + # expectation:false + await _atable_exists(connection, "Hello") + + # 4. Invalid Table Name + # Expectation:ORA-00903: invalid table name + with pytest.raises(oracledb.Error): + await _atable_exists(connection, "123") + + # 5. Empty String + # Expectation:ORA-00903: invalid table name + with pytest.raises(oracledb.Error): + await _atable_exists(connection, "") + + # 6. Special Character + # Expectation:ORA-00911: #: invalid character after FROM + with pytest.raises(oracledb.Error): + await _atable_exists(connection, "##4") + + # 7. Table name length > 128 + # Expectation:ORA-00972: The identifier XXXXXXXXXX...XXXXXXXXXX... + # exceeds the maximum length of 128 bytes. + with pytest.raises(oracledb.Error): + await _atable_exists(connection, "x" * 129) + + # 8. + # Expectation:True + await _acreate_table(connection, "TB1", 65535) + + # 9. Toggle Case (like TaBlE) + # Expectation:True + await _atable_exists(connection, "Tb1") + await adrop_table_purge(connection, "TB1") + + # 10. Table_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" + # Expectation:True + await _acreate_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) + await _atable_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + await adrop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + + ############################ ####### create_table ####### ############################ def test_create_table_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -237,17 +293,134 @@ def test_create_table_test() -> None: drop_table_purge(connection, "YaSh".replace("aS", "ok")) +@pytest.mark.asyncio +async def test_create_table_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + + # 1. New table - HELLO + # Dimension - 100 + # Expectation:table is created + await _acreate_table(connection, "HELLO", 100) + + # 2. Existing table name + # HELLO + # Dimension - 110 + # Expectation:Nothing happens + await _acreate_table(connection, "HELLO", 110) + await adrop_table_purge(connection, "HELLO") + + # 3. New Table - 123 + # Dimension - 100 + # Expectation:ORA-00903: invalid table name + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "123", 100) + await adrop_table_purge(connection, "123") + + # 4. New Table - Hello123 + # Dimension - 65535 + # Expectation:table is created + await _acreate_table(connection, "Hello123", 65535) + await adrop_table_purge(connection, "Hello123") + + # 5. New Table - T1 + # Dimension - 65536 + # Expectation:ORA-51801: VECTOR column type specification + # has an unsupported dimension count ('65536'). + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "T1", 65536) + await adrop_table_purge(connection, "T1") + + # 6. New Table - T1 + # Dimension - 0 + # Expectation:ORA-51801: VECTOR column type specification has + # an unsupported dimension count (0). + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "T1", 0) + await adrop_table_purge(connection, "T1") + + # 7. New Table - T1 + # Dimension - -1 + # Expectation:ORA-51801: VECTOR column type specification has + # an unsupported dimension count ('-'). + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "T1", -1) + await adrop_table_purge(connection, "T1") + + # 8. New Table - T2 + # Dimension - '1000' + # Expectation:table is created + await _acreate_table(connection, "T2", int("1000")) + await adrop_table_purge(connection, "T2") + + # 9. New Table - T3 + # Dimension - 100 passed as a variable + # Expectation:table is created + val = 100 + await _acreate_table(connection, "T3", val) + await adrop_table_purge(connection, "T3") + + # 10. + # Expectation:ORA-00922: missing or invalid option + val2 = """H + ello""" + with pytest.raises(oracledb.Error): + await _acreate_table(connection, val2, 545) + await adrop_table_purge(connection, val2) + + # 11. New Table - เคนเคฟเคจเฅเคฆเฅ€ + # Dimension - 545 + # Expectation:table is created + await _acreate_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) + await adrop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + + # 12. + # Expectation:failure - user does not exist + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "U1.TB4", 128) + await adrop_table_purge(connection, "U1.TB4") + + # 13. + # Expectation:table is created + await _acreate_table(connection, '"T5"', 128) + await adrop_table_purge(connection, '"T5"') + + # 14. Toggle Case + # Expectation:table creation fails + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "TaBlE", 128) + await adrop_table_purge(connection, "TaBlE") + + # 15. table_name as empty_string + # Expectation: ORA-00903: invalid table name + with pytest.raises(oracledb.Error): + await _acreate_table(connection, "", 128) + await adrop_table_purge(connection, "") + await _acreate_table(connection, '""', 128) + await adrop_table_purge(connection, '""') + + # 16. Arithmetic Operations in dimension parameter + # Expectation:table is created + n = 1 + await _acreate_table(connection, "T10", n + 500) + await adrop_table_purge(connection, "T10") + + # 17. String Operations in table_name&dimension parameter + # Expectation:table is created + await _acreate_table(connection, "YaSh".replace("aS", "ok"), 500) + await adrop_table_purge(connection, "YaSh".replace("aS", "ok")) + + ################################## ####### create_hnsw_index ####### ################################## def test_create_hnsw_index_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -519,82 +692,370 @@ def test_create_hnsw_index_test() -> None: drop_table_purge(connection, "TB18") -################################## -####### index_exists ############# -################################## - - -def test_index_exists_test() -> None: - try: - import oracledb - except ImportError: - return - +@pytest.mark.asyncio +async def test_create_hnsw_index_test_async() -> None: try: - connection = oracledb.connect(user=username, password=password, dsn=dsn) + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) except Exception: sys.exit(1) + # 1. Table_name - TB1 + # New Index + # distance_strategy - DistanceStrategy.Dot_product + # Expectation:Index created model1 = HuggingFaceEmbeddings( model_name="sentence-transformers/paraphrase-mpnet-base-v2" ) - # 1. Existing Index:(all capital letters) - # Expectation:true - vs = OracleVS(connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) - create_index(connection, vs, params={"idx_name": "idx11", "idx_type": "HNSW"}) - _index_exists(connection, "IDX11") + vs = await OracleVS.acreate( + connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index(connection, vs) - # 2. Existing Table:(all small letters) - # Expectation:true - _index_exists(connection, "idx11") + # 2. Creating same index again + # Table_name - TB1 + # Expectation:Nothing happens + await acreate_index(connection, vs) + await adrop_index_if_exists(connection, "HNSW") + await adrop_table_purge(connection, "TB1") - # 3. Non-Existing Index - # Expectation:False - _index_exists(connection, "Hello") + # 3. Create index with following parameters: + # idx_name - hnsw_idx2 + # idx_type - HNSW + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} + ) + await adrop_index_if_exists(connection, "hnsw_idx2") + await adrop_table_purge(connection, "TB2") - # 4. Invalid Index Name - # Expectation:Error - try: - _index_exists(connection, "123") - except Exception: - pass + # 4. Table Name - TB1 + # idx_name - "เคนเคฟเคจเฅเคฆเฅ€" + # idx_type - HNSW + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB3", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"} + ) + await adrop_index_if_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + await adrop_table_purge(connection, "TB3") - # 5. Empty String - # Expectation:Error - try: - _index_exists(connection, "") - except Exception: - pass - try: - _index_exists(connection, "") - except Exception: - pass + # 5. idx_name passed empty + # Expectation:ORA-01741: illegal zero-length identifier + with pytest.raises(oracledb.Error): + vs = await OracleVS.acreate( + connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, params={"idx_name": '""', "idx_type": "HNSW"} + ) + await adrop_index_if_exists(connection, '""') + await adrop_table_purge(connection, "TB4") - # 6. Special Character - # Expectation:Error - try: - _index_exists(connection, "##4") - except Exception: - pass + # 6. idx_type left empty + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index(connection, vs, params={"idx_name": "Hello", "idx_type": ""}) + await adrop_index_if_exists(connection, "Hello") + await adrop_table_purge(connection, "TB5") - # 7. Index name length > 128 - # Expectation:Error - try: - _index_exists(connection, "x" * 129) - except Exception: - pass + # 7. efconstruction passed as parameter but not neighbours + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, + vs, + params={"idx_name": "idx11", "efConstruction": 100, "idx_type": "HNSW"}, + ) + await adrop_index_if_exists(connection, "idx11") + await adrop_table_purge(connection, "TB7") - # 8. - # Expectation:true - _index_exists(connection, "U1.IDX11") + # 8. efconstruction passed as parameter as well as neighbours + # (for this idx_type parameter is also necessary) + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB8", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, + vs, + params={ + "idx_name": "idx11", + "efConstruction": 100, + "neighbors": 80, + "idx_type": "HNSW", + }, + ) + await adrop_index_if_exists(connection, "idx11") + await adrop_table_purge(connection, "TB8") - # 9. Toggle Case (like iDx11) - # Expectation:true - _index_exists(connection, "IdX11") + # 9. Limit of Values for(integer values): + # parallel + # efConstruction + # Neighbors + # Accuracy + # 0 + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB15", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, + vs, + params={ + "idx_name": "idx11", + "efConstruction": 200, + "neighbors": 100, + "idx_type": "HNSW", + "parallel": 8, + "accuracy": 10, + }, + ) + await adrop_index_if_exists(connection, "idx11") + await adrop_table_purge(connection, "TB15") + + # 11. index_name as + # Expectation:U1 not present + with pytest.raises(oracledb.Error): + vs = await OracleVS.acreate( + connection, model1, "U1.TB16", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, + vs, + params={ + "idx_name": "U1.idx11", + "efConstruction": 200, + "neighbors": 100, + "idx_type": "HNSW", + "parallel": 8, + "accuracy": 10, + }, + ) + await adrop_index_if_exists(connection, "U1.idx11") + await adrop_table_purge(connection, "TB16") + + # 12. Index_name size >129 + # Expectation:Index not created + with pytest.raises(oracledb.Error): + vs = await OracleVS.acreate( + connection, model1, "TB17", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, params={"idx_name": "x" * 129, "idx_type": "HNSW"} + ) + await adrop_index_if_exists(connection, "x" * 129) + await adrop_table_purge(connection, "TB17") + + # 13. Index_name size 128 + # Expectation:Index created + vs = await OracleVS.acreate( + connection, model1, "TB18", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, params={"idx_name": "x" * 128, "idx_type": "HNSW"} + ) + await adrop_index_if_exists(connection, "x" * 128) + await adrop_table_purge(connection, "TB18") + + +################################## +####### index_exists ############# +################################## + + +def test_index_exists_test() -> None: + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + # 1. Existing Index:(all capital letters) + # Expectation:true + vs = OracleVS(connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "idx11", "idx_type": "HNSW"}) + _index_exists(connection, "IDX11") + + # 2. Existing Table:(all small letters) + # Expectation:true + _index_exists(connection, "idx11") + + # 3. Non-Existing Index + # Expectation:False + _index_exists(connection, "Hello") + + # 4. Invalid Index Name + # Expectation:Error + try: + _index_exists(connection, "123") + except Exception: + pass + + # 5. Empty String + # Expectation:Error + try: + _index_exists(connection, "") + except Exception: + pass + try: + _index_exists(connection, "") + except Exception: + pass + + # 6. Special Character + # Expectation:Error + try: + _index_exists(connection, "##4") + except Exception: + pass + + # 7. Index name length > 128 + # Expectation:Error + try: + _index_exists(connection, "x" * 129) + except Exception: + pass + + # 8. + # Expectation:true + _index_exists(connection, "U1.IDX11") + + # 9. Toggle Case (like iDx11) + # Expectation:true + _index_exists(connection, "IdX11") + + # 10. Index_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" + # Expectation:true + drop_index_if_exists(connection, "idx11") + try: create_index(connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"}) _index_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') except Exception: @@ -602,17 +1063,76 @@ def test_index_exists_test() -> None: drop_table_purge(connection, "TB1") +@pytest.mark.asyncio +async def test_index_exists_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + # 1. Existing Index:(all capital letters) + # Expectation:true + vs = await OracleVS.acreate( + connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, params={"idx_name": "idx11", "idx_type": "HNSW"} + ) + assert await _aindex_exists(connection, "IDX11") + + # 2. Existing Table:(all small letters) + # Expectation:true + assert await _aindex_exists(connection, "idx11") + + # 3. Non-Existing Index + # Expectation:False + assert not await _aindex_exists(connection, "Hello") + + # 4. Invalid Index Name + # Expectation:Error + assert not await _aindex_exists(connection, "123") + + # 5. Empty String + # Expectation:Error + assert not await _aindex_exists(connection, "") + + # 6. Special Character + # Expectation:Error + assert not await _aindex_exists(connection, "##4") + + # 7. Index name length > 128 + # Expectation:Error + assert not await _aindex_exists(connection, "x" * 129) + + # 8. + # Expectation:true + # assert await _aindex_exists(connection, "ONNXUSER.IDX11") + + # 9. Toggle Case (like iDx11) + # Expectation:true + assert await _aindex_exists(connection, "IdX11") + + # 10. Index_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" + # Expectation:true + await adrop_index_if_exists(connection, "idx11") + # with pytest.raises(): # DOESNT? + await acreate_index( + connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"} + ) + await _aindex_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + await adrop_table_purge(connection, "TB1") + + ################################## ####### add_texts ################ ################################## def test_add_texts_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -751,15 +1271,162 @@ def add1(val: str) -> None: pass +@pytest.mark.asyncio +async def test_add_texts_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + # 1. Add 2 records to table + # Expectation:Successful + texts = ["Rohan", "Shailendra"] + metadata = [ + {"id": "100", "link": "Document Example Test 1"}, + {"id": "101", "link": "Document Example Test 2"}, + ] + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = await OracleVS.acreate( + connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await vs_obj.aadd_texts(texts, metadata) + await adrop_table_purge(connection, "TB1") + + # 2. Add record but metadata is not there + # Expectation:An exception occurred :: Either specify an 'ids' list or + # 'metadatas' with an 'id' attribute for each element. + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = await OracleVS.acreate( + connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts2 = ["Sri Ram", "Krishna"] + await vs_obj.aadd_texts(texts2) + await adrop_table_purge(connection, "TB2") + + # 3. Add record with ids option + # ids are passed as string + # ids are passed as empty string + # ids are passed as multi-line string + # ids are passed as "" + # Expectations: + # Successful + # Successful + # Successful + # Successful + + vs_obj = await OracleVS.acreate( + connection, model, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + ids3 = ["114", "124"] + await vs_obj.aadd_texts(texts2, ids=ids3) + await adrop_table_purge(connection, "TB4") + + vs_obj = await OracleVS.acreate( + connection, model, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + ids4 = ["", "134"] + await vs_obj.aadd_texts(texts2, ids=ids4) + await adrop_table_purge(connection, "TB5") + + vs_obj = await OracleVS.acreate( + connection, model, "TB6", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + ids5 = [ + """Good afternoon + my friends""", + "India", + ] + await vs_obj.aadd_texts(texts2, ids=ids5) + await adrop_table_purge(connection, "TB6") + + vs_obj = await OracleVS.acreate( + connection, model, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + ids6 = ['"Good afternoon"', '"India"'] + await vs_obj.aadd_texts(texts2, ids=ids6) + await adrop_table_purge(connection, "TB7") + + # 4. Add records with ids and metadatas + # Expectation:Successful + vs_obj = await OracleVS.acreate( + connection, model, "TB8", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts3 = ["Sri Ram 6", "Krishna 6"] + ids7 = ["1", "2"] + metadata = [ + {"id": "102", "link": "Document Example", "stream": "Science"}, + {"id": "104", "link": "Document Example 45"}, + ] + await vs_obj.aadd_texts(texts3, metadata, ids=ids7) + await adrop_table_purge(connection, "TB8") + + # 5. Add 10000 records + # Expectation:Successful + vs_obj = await OracleVS.acreate( + connection, model, "TB9", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts4 = ["Sri Ram{0}".format(i) for i in range(1, 10000)] + ids8 = ["Hello{0}".format(i) for i in range(1, 10000)] + await vs_obj.aadd_texts(texts4, ids=ids8) + await adrop_table_purge(connection, "TB9") + + # 6. Add 2 different record concurrently + # Expectation:Successful + async def add(val: str) -> None: + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/all-mpnet-base-v2" + ) + vs_obj = await OracleVS.acreate( + connection, model, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts5 = [val] + ids9 = texts5 + await vs_obj.aadd_texts(texts5, ids=ids9) + + task_1 = asyncio.create_task(add("Sri Ram")) + task_2 = asyncio.create_task(add("Sri Krishna")) + + await asyncio.gather(task_1, task_2) + await adrop_table_purge(connection, "TB10") + + # 7. Add 2 same record concurrently + # Expectation:Successful, For one of the insert,get primary key violation error + async def add1(val: str) -> None: + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/all-mpnet-base-v2" + ) + vs_obj = await OracleVS.acreate( + connection, model, "TB11", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts = [val] + ids10 = texts + await vs_obj.aadd_texts(texts, ids=ids10) + + with pytest.raises(oracledb.Error): + task_1 = asyncio.create_task(add1("Sri Ram")) + task_2 = asyncio.create_task(add1("Sri Ram")) + await asyncio.gather(task_1, task_2) + + await adrop_table_purge(connection, "TB11") + + # 8. create object with table name of type + # Expectation:U1 does not exist + with pytest.raises(oracledb.Error): + vs_obj = await OracleVS.acreate( + connection, model, "U1.TB14", DistanceStrategy.DOT_PRODUCT + ) + for i in range(1, 10): + texts7 = ["Yash{0}".format(i)] + ids13 = ["1234{0}".format(i)] + await vs_obj.aadd_texts(texts7, ids=ids13) + await adrop_table_purge(connection, "TB14") + + ################################## ####### embed_documents(text) #### ################################## def test_embed_documents_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -775,15 +1442,31 @@ def test_embed_documents_test() -> None: drop_table_purge(connection, "TB7") +@pytest.mark.asyncio +async def test_embed_documents_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + # 1. String Example-'Sri Ram' + # Expectation:Vector Printed + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = await OracleVS.acreate( + connection, model, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + + # 4. List + # Expectation:Vector Printed + await vs_obj._aembed_documents(["hello", "yash"]) + await adrop_table_purge(connection, "TB7") + + ################################## ####### embed_query(text) ######## ################################## def test_embed_query_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -800,15 +1483,32 @@ def test_embed_query_test() -> None: vs_obj._embed_query("") +@pytest.mark.asyncio +async def test_embed_query_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + # 1. String + # Expectation:Vector printed + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + vs_obj = await OracleVS.acreate( + connection, model, "TB8", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await vs_obj._aembed_query("Sri Ram") + await adrop_table_purge(connection, "TB8") + + # 3. Empty string + # Expectation:[] + await vs_obj._aembed_query("") + + ################################## ####### create_index ############# ################################## def test_create_index_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -878,15 +1578,97 @@ def test_create_index_test() -> None: drop_table_purge(connection, "TB7") +@pytest.mark.asyncio +async def test_create_index_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + # 1. No optional parameters passed + # Expectation:Successful + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + vs = await OracleVS.acreate( + connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index(connection, vs) + await adrop_index_if_exists(connection, "HNSW") + await adrop_table_purge(connection, "TB1") + + # 2. ivf index + # Expectation:Successful + vs = await OracleVS.acreate( + connection, model1, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index(connection, vs, {"idx_type": "IVF", "idx_name": "IVF"}) + await adrop_index_if_exists(connection, "IVF") + await adrop_table_purge(connection, "TB2") + + # 3. ivf index with neighbour_part passed as parameter + # Expectation:Successful + vs = await OracleVS.acreate( + connection, model1, "TB3", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index(connection, vs, {"idx_type": "IVF", "neighbor_part": 10}) + await adrop_index_if_exists(connection, "IVF") + await adrop_table_purge(connection, "TB3") + + # 4. ivf index with neighbour_part and accuracy passed as parameter + # Expectation:Successful + vs = await OracleVS.acreate( + connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, {"idx_type": "IVF", "neighbor_part": 10, "accuracy": 90} + ) + await adrop_index_if_exists(connection, "IVF") + await adrop_table_purge(connection, "TB4") + + # 5. ivf index with neighbour_part and parallel passed as parameter + # Expectation:Successful + vs = await OracleVS.acreate( + connection, model1, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, vs, {"idx_type": "IVF", "neighbor_part": 10, "parallel": 90} + ) + await adrop_index_if_exists(connection, "IVF") + await adrop_table_purge(connection, "TB5") + + # 6. ivf index and then perform dml(insert) + # Expectation:Successful + vs = await OracleVS.acreate( + connection, model1, "TB6", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index(connection, vs, {"idx_type": "IVF", "idx_name": "IVF"}) + texts = ["Sri Ram", "Krishna"] + await vs.aadd_texts(texts) + # perform delete + await vs.adelete(["hello"]) + await adrop_index_if_exists(connection, "IVF") + await adrop_table_purge(connection, "TB6") + + # 7. ivf index with neighbour_part,parallel and accuracy passed as parameter + # Expectation:Successful + vs = await OracleVS.acreate( + connection, model1, "TB7", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + await acreate_index( + connection, + vs, + {"idx_type": "IVF", "neighbor_part": 10, "parallel": 90, "accuracy": 99}, + ) + await adrop_index_if_exists(connection, "IVF") + await adrop_table_purge(connection, "TB7") + + ################################## ####### perform_search ########### ################################## def test_perform_search_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -956,15 +1738,96 @@ def test_perform_search_test() -> None: drop_table_purge(connection, "TB15") +@pytest.mark.asyncio +async def test_perform_search_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + + vs_1 = await OracleVS.acreate( + connection, model1, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + vs_2 = await OracleVS.acreate( + connection, model1, "TB11", DistanceStrategy.DOT_PRODUCT + ) + vs_3 = await OracleVS.acreate(connection, model1, "TB12", DistanceStrategy.COSINE) + vs_4 = await OracleVS.acreate( + connection, model1, "TB13", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + vs_5 = await OracleVS.acreate( + connection, model1, "TB14", DistanceStrategy.DOT_PRODUCT + ) + vs_6 = await OracleVS.acreate(connection, model1, "TB15", DistanceStrategy.COSINE) + + # vector store lists: + vs_list = [vs_1, vs_2, vs_3, vs_4, vs_5, vs_6] + + for i, vs in enumerate(vs_list, start=1): + # insert data + texts = ["Yash", "Varanasi", "Yashaswi", "Mumbai", "BengaluruYash"] + metadatas = [ + {"id": "hello"}, + {"id": "105"}, + {"id": "106"}, + {"id": "yash"}, + {"id": "108"}, + ] + + await vs.aadd_texts(texts, metadatas) + + # create index + if i == 1 or i == 2 or i == 3: + await acreate_index( + connection, vs, {"idx_type": "HNSW", "idx_name": f"IDX1{i}"} + ) + else: + await acreate_index( + connection, vs, {"idx_type": "IVF", "idx_name": f"IDX1{i}"} + ) + + # perform search + query = "YashB" + + filter = {"id": ["106", "108", "yash"]} + + # similarity_searh without filter + await vs.asimilarity_search(query, 2) + + # similarity_searh with filter + await vs.asimilarity_search(query, 2, filter=filter) + + # Similarity search with relevance score + await vs.asimilarity_search_with_score(query, 2) + + # Similarity search with relevance score with filter + await vs.asimilarity_search_with_score(query, 2, filter=filter) + + # Max marginal relevance search + await vs.amax_marginal_relevance_search(query, 2, fetch_k=20, lambda_mult=0.5) + + # Max marginal relevance search with filter + await vs.amax_marginal_relevance_search( + query, 2, fetch_k=20, lambda_mult=0.5, filter=filter + ) + + await adrop_table_purge(connection, "TB10") + await adrop_table_purge(connection, "TB11") + await adrop_table_purge(connection, "TB12") + await adrop_table_purge(connection, "TB13") + await adrop_table_purge(connection, "TB14") + await adrop_table_purge(connection, "TB15") + + ################################## ##### perform_filter_search ###### ################################## def test_db_filter_test() -> None: - try: - import oracledb - except ImportError: - return - try: connection = oracledb.connect(user=username, password=password, dsn=dsn) except Exception: @@ -1078,3 +1941,355 @@ def test_db_filter_test() -> None: drop_table_purge(connection, "TB13") drop_table_purge(connection, "TB14") drop_table_purge(connection, "TB15") + + +@pytest.mark.asyncio +async def test_db_filter_test_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + model1 = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + + vs_1 = await OracleVS.acreate( + connection, model1, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + vs_2 = await OracleVS.acreate( + connection, model1, "TB11", DistanceStrategy.DOT_PRODUCT + ) + vs_3 = await OracleVS.acreate(connection, model1, "TB12", DistanceStrategy.COSINE) + vs_4 = await OracleVS.acreate( + connection, model1, "TB13", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + vs_5 = await OracleVS.acreate( + connection, model1, "TB14", DistanceStrategy.DOT_PRODUCT + ) + vs_6 = await OracleVS.acreate(connection, model1, "TB15", DistanceStrategy.COSINE) + + # vector store lists: + vs_list = [vs_1, vs_2, vs_3, vs_4, vs_5, vs_6] + + for i, vs in enumerate(vs_list): + # insert data + texts = ["Strawberry", "Banana", "Blueberry", "Grape", "Watermelon"] + metadatas = [ + {"id": "st", "order": 1}, + {"id": "ba", "order": 2}, + {"id": "bl", "order": 3}, + {"id": "gr", "order": 4}, + {"id": "wa", "order": 5}, + ] + + await vs.aadd_texts(texts, metadatas) + + # create index + if i == 1 or i == 2 or i == 3: + await acreate_index( + connection, vs, {"idx_type": "HNSW", "idx_name": f"IDX1{i}"} + ) + else: + await acreate_index( + connection, vs, {"idx_type": "IVF", "idx_name": f"IDX1{i}"} + ) + + # perform search + query = "Strawberry" + + filter = {"id": ["bl"]} + db_filter = {"key": "id", "oper": "EQ", "value": "bl"} # FilterCondition + + # similarity_search without filter + result = await vs.asimilarity_search(query, 1) + assert result[0].metadata["id"] == "st" + + # similarity_search with filter + result = await vs.asimilarity_search(query, 1, filter=filter) + assert len(result) == 0 + + # similarity_search with db_filter + result = await vs.asimilarity_search(query, 1, db_filter=db_filter) + assert result[0].metadata["id"] == "bl" + + # similarity_search with filter and db_filter + result = await vs.asimilarity_search( + query, 1, filter=filter, db_filter=db_filter + ) + assert result[0].metadata["id"] == "bl" + + # nested db filter + db_filter_nested = { + "_or": [ + {"key": "id", "oper": "EQ", "value": "ba"}, # FilterCondition + { + "_and": [ # FilterGroup + {"key": "order", "oper": "LTE", "value": 4}, + {"key": "id", "oper": "EQ", "value": "st"}, + ] + }, + ] + } + + # similarity_search with db_filter + result = await vs.asimilarity_search(query, 1, db_filter=db_filter_nested) + assert result[0].metadata["id"] == "st" + + exception_occurred = False + try: + db_filter_exc = { + "_xor": [ # Incorrect operation _xor + {"key": "id", "oper": "EQ", "value": "ba"}, + {"key": "order", "oper": "LTE", "value": 4}, + ] + } + result = await vs.asimilarity_search(query, 1, db_filter=db_filter_exc) + except ValueError: + exception_occurred = True + + assert exception_occurred + + exception_occurred = False + try: + db_filter_exc = { + "_or": [ + { + "key": "id", + "oper": "XEQ", + "value": "ba", + }, # Incorrect operation XEQ + {"key": "order", "oper": "LTE", "value": 4}, + ] + } + result = await vs.asimilarity_search(query, 1, db_filter=db_filter_exc) + except ValueError: + exception_occurred = True + + assert exception_occurred + + await adrop_table_purge(connection, "TB10") + await adrop_table_purge(connection, "TB11") + await adrop_table_purge(connection, "TB12") + await adrop_table_purge(connection, "TB13") + await adrop_table_purge(connection, "TB14") + await adrop_table_purge(connection, "TB15") + + +################################## +##### test_pool_connections ###### +################################## + + +@pytest.mark.asyncio +async def atest_add_texts_pool_test() -> None: + POOLS_MAX = 4 + + try: + connection = oracledb.create_pool( + user=username, password=password, dsn=dsn, min=1, max=POOLS_MAX, increment=1 + ) + except Exception: + sys.exit(1) + + # 1. Add different records concurrently + # Expectation:Successful + async def add(order: int) -> None: + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/all-mpnet-base-v2" + ) + vs_obj = OracleVS( + connection, model, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts = ["Sri Ram" + str(order)] + ids = texts + vs_obj.add_texts(texts, ids=ids) + + tasks = [] + + for i in range(POOLS_MAX + 2): + task = asyncio.create_task(add(i)) + tasks.append(task) + + await asyncio.gather(*tasks, return_exceptions=True) + + assert connection.busy == 0 + + with connection.acquire() as _conn: + with _conn.cursor() as _conncursor: + _conncursor.execute("select count(*) from TB10") + count = _conncursor.fetchone() + + assert count[0] == POOLS_MAX + 2 + + drop_table_purge(connection, "TB10") + + connection.close() + + +@pytest.mark.asyncio +async def test_add_texts_pool_test_async() -> None: + POOLS_MAX = 4 + + try: + connection = oracledb.create_pool_async( + user=username, password=password, dsn=dsn, min=1, max=4, increment=1 + ) + except Exception: + sys.exit(1) + + # 1. Add different records concurrently + # Expectation:Successful + async def add(order: int) -> None: + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/all-mpnet-base-v2" + ) + vs_obj = await OracleVS.acreate( + connection, model, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + texts = ["Sri Ram" + str(order)] + ids = texts + await vs_obj.aadd_texts(texts, ids=ids) + + tasks = [] + + for i in range(POOLS_MAX + 2): + task = asyncio.create_task(add(i)) + tasks.append(task) + + await asyncio.gather(*tasks, return_exceptions=True) + + assert connection.busy == 0 + + async with connection.acquire() as _conn: + count = await _conn.fetchone("select count(*) from TB10") + + assert count[0] == POOLS_MAX + 2 + + await adrop_table_purge(connection, "TB10") + + await connection.close() + + +################################## +##### test_from_texts_lobs ###### +################################## + + +def test_from_texts_lobs() -> None: + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + + texts = [ + "If the answer to any preceding questions is yes, then the database stops \ + the search and allocates space from the specified tablespace; otherwise, \ + space is allocated from the database default shared temporary tablespace.", + "A tablespace can be online (accessible) or offline (not accessible) whenever \ + the database is open.\nA tablespace is usually online so that its data is \ + available to users. The SYSTEM tablespace and temporary tablespaces cannot \ + be taken offline.", + ] + + metadatas = [ + { + "id": "cncpt_15.5.3.2.2_P4", + "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-5387D7B2-C0CA-4C1E-811B-C7EB9B636442", # type: ignore[E501] + }, + { + "id": "cncpt_15.5.5_P1", + "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-D02B2220-E6F5-40D9-AFB5-BC69BCEF6CD4", # type: ignore[E501] + }, + ] + + vs = OracleVS.from_texts( + texts, + model, + metadatas, + client=connection, + table_name="TB10", + distance_strategy=DistanceStrategy.COSINE, + ) + + create_index(connection, vs, {"idx_type": "HNSW", "idx_name": "IDX1"}) + + query = "What is a tablespace?" + + # 1. Test when oracledb.defaults.fetch_lobs is set to False + # Expectation:Successful + oracledb.defaults.fetch_lobs = False + # similarity_search without filter + res = vs.similarity_search(query, 2) + + assert len(res) == 2 + assert any("tablespace can be online" in str(r.page_content) for r in res) + + drop_table_purge(connection, "TB10") + + oracledb.defaults.fetch_lobs = True + + +@pytest.mark.asyncio +async def test_from_texts_lobs_async() -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + + model = HuggingFaceEmbeddings( + model_name="sentence-transformers/paraphrase-mpnet-base-v2" + ) + + texts = [ + "If the answer to any preceding questions is yes, then the database stops \ + the search and allocates space from the specified tablespace; otherwise, \ + space is allocated from the database default shared temporary tablespace.", + "A tablespace can be online (accessible) or offline (not accessible) whenever \ + the database is open.\nA tablespace is usually online so that its data is \ + available to users. The SYSTEM tablespace and temporary tablespaces cannot \ + be taken offline.", + ] + + metadatas = [ + { + "id": "cncpt_15.5.3.2.2_P4", + "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-5387D7B2-C0CA-4C1E-811B-C7EB9B636442", + }, + { + "id": "cncpt_15.5.5_P1", + "link": "https://docs.oracle.com/en/database/oracle/oracle-database/23/cncpt/logical-storage-structures.html#GUID-D02B2220-E6F5-40D9-AFB5-BC69BCEF6CD4", + }, + ] + + vs = await OracleVS.afrom_texts( + texts, + model, + metadatas, + client=connection, + table_name="TB10", + distance_strategy=DistanceStrategy.COSINE, + ) + + await acreate_index(connection, vs, {"idx_type": "HNSW", "idx_name": "IDX1"}) + + query = "What is a tablespace?" + + # 1. Test when oracledb.defaults.fetch_lobs is set to False + oracledb.defaults.fetch_lobs = False + # similarity_search without filter + res = await vs.asimilarity_search(query, 2) + + assert len(res) == 2 + assert any("tablespace can be online" in str(r.page_content) for r in res) + + await adrop_table_purge(connection, "TB10") + + oracledb.defaults.fetch_lobs = True From f3ff4050fd9b26d609f98a9bc525467c1894c3d0 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Fri, 11 Jul 2025 12:54:15 +0000 Subject: [PATCH 04/12] Convert execute to executemany --- .../langchain_oracledb/utilities/oracleai.py | 63 +++++++++---------- .../document_loaders/test_oracleds.py | 9 +++ 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/utilities/oracleai.py b/libs/oracledb/langchain_oracledb/utilities/oracleai.py index 3d7a942..83ed40f 100644 --- a/libs/oracledb/langchain_oracledb/utilities/oracleai.py +++ b/libs/oracledb/langchain_oracledb/utilities/oracleai.py @@ -110,47 +110,40 @@ def get_summary(self, docs: Any) -> List[str]: results.append(str(summary.getvalue())) elif isinstance(docs, List): - results = [] + docs_input = [] + params = json.dumps(self.summary_params) + summary = cursor.var(oracledb.DB_TYPE_CLOB, arraysize=len(docs)) - for doc in docs: - summary = cursor.var(oracledb.DB_TYPE_CLOB) + for i, doc in enumerate(docs): if isinstance(doc, str): - cursor.execute( - """ - declare - input clob; - begin - input := :data; - :summ := dbms_vector_chain.utl_to_summary(input, - json(:params)); - end;""", - data=doc, - params=json.dumps(self.summary_params), - summ=summary, - ) - + docs_input.append((doc, params)) elif isinstance(doc, Document): - cursor.execute( - """ - declare - input clob; - begin - input := :data; - :summ := dbms_vector_chain.utl_to_summary(input, - json(:params)); - end;""", - data=doc.page_content, - params=json.dumps(self.summary_params), - summ=summary, - ) - + docs_input.append((doc.page_content, params)) else: raise Exception("Invalid input type") - if summary is None: - results.append("") - else: - results.append(str(summary.getvalue())) + cursor.setinputsizes(None, None, summary) + + cursor.executemany( + """ + declare + input clob; + summ clob; + begin + input := :1; + summ := dbms_vector_chain.utl_to_summary(input, + json(:2)); + :3 := summ; + end;""", + docs_input, + ) + + value = summary.getvalue(i) + + results = [ + "" if value is None else str(value) + for i in range(summary.actual_elements) + ] else: raise Exception("Invalid input type") diff --git a/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py b/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py index 32b34a6..0cf4350 100644 --- a/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py +++ b/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py @@ -340,6 +340,15 @@ def test_summary_test() -> None: summaries = summary.get_summary(doc) + # verify + if len(summaries) == 0: + sys.exit(1) + + summaries = summary.get_summary([doc, doc]) + + if len(summaries) != 2: + sys.exit(1) + # verify if len(summaries) == 0: sys.exit(1) From 90c370ec0486ed988fa9a4903988ba9e14773765 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Tue, 15 Jul 2025 08:53:55 +0000 Subject: [PATCH 05/12] Fix formatting issues --- README.md | 4 +- libs/oracledb/LICENSE | 21 --- libs/oracledb/LICENSE.txt | 35 +++++ libs/oracledb/README.md | 22 +-- .../document_loaders/oracleadb_loader.py | 8 +- .../langchain_oracledb/embeddings/oracleai.py | 6 +- .../vectorstores/oraclevs.py | 142 +++++++++--------- 7 files changed, 126 insertions(+), 112 deletions(-) delete mode 100644 libs/oracledb/LICENSE create mode 100644 libs/oracledb/LICENSE.txt diff --git a/README.md b/README.md index 36f95ca..3e8dc11 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,13 @@ Welcome to the official repository for LangChain integration with [Oracle Cloud For OCI services: ```bash -pip install -U langchain-oci +python -m pip install -U langchain-oci ``` For Oracle AI Vector Search services: ```bash -pip install -U langchain-oracledb +python -m pip install -U langchain-oracledb ``` --- diff --git a/libs/oracledb/LICENSE b/libs/oracledb/LICENSE deleted file mode 100644 index fc0602f..0000000 --- a/libs/oracledb/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 LangChain, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/oracledb/LICENSE.txt b/libs/oracledb/LICENSE.txt new file mode 100644 index 0000000..aa487cc --- /dev/null +++ b/libs/oracledb/LICENSE.txt @@ -0,0 +1,35 @@ +Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. + +The Universal Permissive License (UPL), Version 1.0 + +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or data +(collectively the "Software"), free of charge and under any and all copyright +rights in the Software, and any and all patent rights owned or freely +licensable by each licensor hereunder covering either (i) the unmodified +Software as contributed to or provided by such licensor, or (ii) the Larger +Works (as defined below), to deal in both + +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), + +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. + +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at +a minimum a reference to the UPL must be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/oracledb/README.md b/libs/oracledb/README.md index 298e010..a0933cc 100644 --- a/libs/oracledb/README.md +++ b/libs/oracledb/README.md @@ -5,7 +5,7 @@ This package contains the LangChain integrations with [Oracle AI Vector Search]( ## Installation ```bash -pip install -U langchain-oracledb +python -m pip install -U langchain-oracledb ``` ## Examples @@ -54,7 +54,7 @@ embedding_model = HuggingFaceEmbeddings( ) vector_store = OracleVS(conn, embedding_model, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE) -# Add texts to the vector database +# add texts to the vector database texts = ["A tablespace can be online (accessible) or offline (not accessible) whenever the database is open.\nA tablespace is usually online so that its data is available to users. The SYSTEM tablespace and temporary tablespaces cannot be taken offline.", "The database stores LOBs differently from other data types. Creating a LOB column implicitly creates a LOB segment and a LOB index. "] metadata = [ {"id": "100", "link": "Document Example Test 1"}, @@ -67,7 +67,7 @@ create_index( conn, vector_store, params={"idx_name": "hnsw_oravs", "idx_type": "HNSW"} ) -# Perform siliarity search +# perform siliarity search vs.similarity_search("How does a database stores LOBs?", 1) ``` @@ -98,11 +98,11 @@ loader_params = { "colname": "data", } -""" load the docs """ +# load the docs loader = OracleDocLoader(conn=conn, params=loader_params) docs = loader.load() -""" verify """ +# verify print(f"Number of docs loaded: {len(docs)}") ``` @@ -121,12 +121,12 @@ loader_params = { "colname": "data", } -""" load the docs """ +# load the docs loader = OracleDocLoader(conn=conn, params=loader_params) docs = loader.load() """ -# Some examples +# some examples # split by chars, max 500 chars splitter_params = {"split": "chars", "max": 500, "normalize": "all"} @@ -148,7 +148,7 @@ for doc in docs: chunks = splitter.split_text(doc.page_content) list_chunks.extend(chunks) -""" verify """ +# verify print(f"Number of Chunks: {len(list_chunks)}") # print(f"Chunk-0: {list_chunks[0]}") # content ``` @@ -218,11 +218,11 @@ embedder_params = { # using ONNX model loaded to Oracle Database embedder_params = {"provider": "database", "model": "demo_model"} -# If a proxy is not required for your environment, you can omit the 'proxy' parameter below +# if a proxy is not required for your environment, you can omit the 'proxy' parameter below embedder = OracleEmbeddings(conn=conn, params=embedder_params, proxy=proxy) embed = embedder.embed_query("Hello World!") -""" verify """ +# verify print(f"Embedding generated by OracleEmbeddings: {embed}") ``` @@ -264,7 +264,7 @@ summary_params = { } # get the summary instance -# Remove proxy if not required +# remove proxy if not required summ = OracleSummary(conn=conn, params=summary_params, proxy=proxy) summary = summ.get_summary( "In the heart of the forest, " diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py index f0f1fe1..c1b9917 100644 --- a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py @@ -50,23 +50,23 @@ def __init__( :param metadata: metadata used in document :param parameters: bind variable to use in query """ - # Mandatory required arguments. + # mandatory required arguments. self.query = query self.user = user self.password = password - # Schema + # schema self.schema = schema # TNS connection Method self.tns_name = tns_name self.config_dir = config_dir - # Wallet configuration is required for mTLS connection + # wallet configuration is required for mTLS connection self.wallet_location = wallet_location self.wallet_password = wallet_password - # Connection String connection method + # connection string connection method self.connection_string = connection_string # metadata column diff --git a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py index c7f9dc6..501dbaf 100644 --- a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py +++ b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py @@ -32,11 +32,11 @@ class OracleEmbeddings(BaseModel, Embeddings): """Get Embeddings""" - """Oracle Connection""" + # Oracle connection conn: Any = None - """Embedding Parameters""" + # embedding parameters params: Dict[str, Any] - """Proxy""" + # proxy proxy: Optional[str] = None def __init__(self, **kwargs: Any): diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index 7d4f231..7a46f43 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -51,7 +51,7 @@ ) -# Define a type variable that can be any kind of function +# define a type variable that can be any kind of function T = TypeVar("T", bound=Callable[..., Any]) @@ -83,7 +83,7 @@ def _generate_condition(condition: FilterCondition) -> str: def _generate_where_clause(db_filter: Union[FilterCondition, FilterGroup]) -> str: - if "key" in db_filter: # Identify as FilterCondition + if "key" in db_filter: # identify as FilterCondition return _generate_condition(cast(FilterCondition, db_filter)) if "_and" in db_filter and db_filter["_and"] is not None: @@ -145,17 +145,17 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: try: return func(*args, **kwargs) except RuntimeError as db_err: - # Handle a known type of error (e.g., DB-related) specifically + # handle a known type of error (e.g., DB-related) specifically logger.exception("DB-related error occurred.") raise RuntimeError( "Failed due to a DB issue: {}".format(db_err) ) from db_err except ValueError as val_err: - # Handle another known type of error specifically + # handle another known type of error specifically logger.exception("Validation error.") raise ValueError("Validation failed: {}".format(val_err)) from val_err except Exception as e: - # Generic handler for all other exceptions + # generic handler for all other exceptions logger.exception("An unexpected error occurred: {}".format(e)) raise RuntimeError("Unexpected error: {}".format(e)) from e @@ -187,24 +187,24 @@ async def _atable_exists(connection: AsyncConnection, table_name: str) -> bool: def _compare_version(version: str, target_version: str) -> bool: - # Split both version strings into parts + # split both version strings into parts version_parts = [int(part) for part in version.split(".")] target_parts = [int(part) for part in target_version.split(".")] - # Compare each part + # compare each part for v, t in zip(version_parts, target_parts): if v < t: - return True # Current version is less + return True # current version is less elif v > t: - return False # Current version is greater + return False # current version is greater - # If all parts equal so far, check if version has fewer parts than target_version + # if all parts equal so far, check if version has fewer parts than target_version return len(version_parts) < len(target_parts) @_handle_exceptions def _index_exists(connection: Connection, index_name: str) -> bool: - # Check if the index exists + # check if the index exists query = """ SELECT index_name FROM all_indexes @@ -212,17 +212,17 @@ def _index_exists(connection: Connection, index_name: str) -> bool: """ with connection.cursor() as cursor: - # Execute the query + # execute the query cursor.execute(query, idx_name=index_name.upper()) result = cursor.fetchone() - # Check if the index exists + # check if the index exists return result is not None @_handle_exceptions async def _aindex_exists(connection: AsyncConnection, index_name: str) -> bool: - # Check if the index exists + # check if the index exists query = """ SELECT index_name FROM all_indexes @@ -230,16 +230,16 @@ async def _aindex_exists(connection: AsyncConnection, index_name: str) -> bool: """ with connection.cursor() as cursor: - # Execute the query + # execute the query await cursor.execute(query, idx_name=index_name.upper()) result = await cursor.fetchone() - # Check if the index exists + # check if the index exists return result is not None def _get_distance_function(distance_strategy: DistanceStrategy) -> str: - # Dictionary to map distance strategies to their corresponding function + # dictionary to map distance strategies to their corresponding function # names distance_strategy2function = { DistanceStrategy.EUCLIDEAN_DISTANCE: "EUCLIDEAN", @@ -247,11 +247,11 @@ def _get_distance_function(distance_strategy: DistanceStrategy) -> str: DistanceStrategy.COSINE: "COSINE", } - # Attempt to return the corresponding distance function + # attempt to return the corresponding distance function if distance_strategy in distance_strategy2function: return distance_strategy2function[distance_strategy] - # If it's an unsupported distance strategy, raise an error + # if it's an unsupported distance strategy, raise an error raise ValueError(f"Unsupported distance strategy: {distance_strategy}") @@ -358,7 +358,7 @@ def _get_hnsw_index_ddl( if params: config = params.copy() - # Ensure compulsory parts are included + # ensure compulsory parts are included for compulsory_key in ["idx_name", "parallel"]: if compulsory_key not in config: if compulsory_key == "idx_name": @@ -368,21 +368,21 @@ def _get_hnsw_index_ddl( else: config[compulsory_key] = defaults[compulsory_key] - # Validate keys in config against defaults + # validate keys in config against defaults for key in config: if key not in defaults: raise ValueError(f"Invalid parameter: {key}") else: config = defaults - # Base SQL statement + # base SQL statement idx_name = config["idx_name"] base_sql = ( f"create vector index {idx_name} on {table_name}(embedding) " f"ORGANIZATION INMEMORY NEIGHBOR GRAPH" ) - # Optional parts depending on parameters + # optional parts depending on parameters accuracy_part = " WITH TARGET ACCURACY {accuracy}" if ("accuracy" in config) else "" distance_part = f" DISTANCE {_get_distance_function(distance_strategy)}" @@ -405,14 +405,14 @@ def _get_hnsw_index_ddl( "neighbors}, efConstruction {efConstruction})" ) - # Always included part for parallel + # always included part for parallel parallel_part = " parallel {parallel}" - # Combine all parts + # combine all parts ddl_assembly = ( base_sql + accuracy_part + distance_part + parameters_part + parallel_part ) - # Format the SQL with values from the params dictionary + # format the SQL with values from the params dictionary ddl = ddl_assembly.format(**config) return idx_name, ddl @@ -427,7 +427,7 @@ def _create_hnsw_index( ) -> None: idx_name, ddl = _get_hnsw_index_ddl(table_name, distance_strategy, params) - # Check if the index exists + # check if the index exists if not _index_exists(connection, idx_name): with connection.cursor() as cursor: cursor.execute(ddl) @@ -441,7 +441,7 @@ def _get_ivf_index_ddl( distance_strategy: DistanceStrategy, params: Optional[dict[str, Any]] = None, ) -> Tuple[str, str]: - # Default configuration + # default configuration defaults = { "idx_name": "IVF", "idx_type": "IVF", @@ -452,7 +452,7 @@ def _get_ivf_index_ddl( if params: config = params.copy() - # Ensure compulsory parts are included + # ensure compulsory parts are included for compulsory_key in ["idx_name", "parallel"]: if compulsory_key not in config: if compulsory_key == "idx_name": @@ -462,21 +462,21 @@ def _get_ivf_index_ddl( else: config[compulsory_key] = defaults[compulsory_key] - # Validate keys in config against defaults + # validate keys in config against defaults for key in config: if key not in defaults: raise ValueError(f"Invalid parameter: {key}") else: config = defaults - # Base SQL statement + # base SQL statement idx_name = config["idx_name"] base_sql = ( f"CREATE VECTOR INDEX {idx_name} ON {table_name}(embedding) " f"ORGANIZATION NEIGHBOR PARTITIONS" ) - # Optional parts depending on parameters + # optional parts depending on parameters accuracy_part = " WITH TARGET ACCURACY {accuracy}" if ("accuracy" in config) else "" distance_part = f" DISTANCE {_get_distance_function(distance_strategy)}" @@ -487,14 +487,14 @@ def _get_ivf_index_ddl( f" partitions {config['neighbor_part']})" ) - # Always included part for parallel + # always included part for parallel parallel_part = f" PARALLEL {config['parallel']}" - # Combine all parts + # combine all parts ddl_assembly = ( base_sql + accuracy_part + distance_part + parameters_part + parallel_part ) - # Format the SQL with values from the params dictionary + # format the SQL with values from the params dictionary ddl = ddl_assembly.format(**config) return idx_name, ddl @@ -509,7 +509,7 @@ def _create_ivf_index( ) -> None: idx_name, ddl = _get_ivf_index_ddl(table_name, distance_strategy, params) - # Check if the index exists + # check if the index exists if not _index_exists(connection, idx_name): with connection.cursor() as cursor: cursor.execute(ddl) @@ -569,7 +569,7 @@ async def _acreate_hnsw_index( ) -> None: idx_name, ddl = _get_hnsw_index_ddl(table_name, distance_strategy, params) - # Check if the index exists + # check if the index exists if not await _aindex_exists(connection, idx_name): with connection.cursor() as cursor: await cursor.execute(ddl) @@ -587,7 +587,7 @@ async def _acreate_ivf_index( ) -> None: idx_name, ddl = _get_ivf_index_ddl(table_name, distance_strategy, params) - # Check if the index exists + # check if the index exists if not await _aindex_exists(connection, idx_name): with connection.cursor() as cursor: await cursor.execute(ddl) @@ -700,19 +700,19 @@ def get_processed_ids( ids: Optional[List[str]] = None, ) -> List[str]: if ids: - # If ids are provided, hash them to maintain consistency + # if ids are provided, hash them to maintain consistency processed_ids = [ hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids ] elif metadatas and all("id" in metadata for metadata in metadatas): - # If no ids are provided but metadatas with ids are, generate + # if no ids are provided but metadatas with ids are, generate # ids from metadatas processed_ids = [ hashlib.sha256(metadata["id"].encode()).hexdigest()[:16].upper() for metadata in metadatas ] else: - # Generate new ids if none are provided + # generate new ids if none are provided generated_ids = [ str(uuid.uuid4()) for _ in texts ] # uuid4 is more standard for random UUIDs @@ -730,15 +730,15 @@ def _get_delete_ddl( if ids is None: raise ValueError("No ids provided to delete.") - # Compute SHA-256 hashes of the ids and truncate them + # compute SHA-256 hashes of the ids and truncate them hashed_ids = [hashlib.sha256(_id.encode()).hexdigest()[:16].upper() for _id in ids] - # Constructing the SQL statement with individual placeholders + # constructing the SQL statement with individual placeholders placeholders = ", ".join([":id" + str(i + 1) for i in range(len(hashed_ids))]) ddl = f"DELETE FROM {table_name} WHERE id IN ({placeholders})" - # Preparing bind variables + # preparing bind variables bind_vars = {f"id{i}": hashed_id for i, hashed_id in enumerate(hashed_ids, start=1)} return ddl, bind_vars @@ -750,13 +750,13 @@ def mmr_from_docs_embeddings( k: int = 4, lambda_mult: float = 0.5, ) -> List[Tuple[Document, float]]: - # If you need to split documents and scores for processing (e.g., + # if you need to split documents and scores for processing (e.g., # for MMR calculation) documents, scores, embeddings = ( zip(*docs_scores_embeddings) if docs_scores_embeddings else ([], [], []) ) - # Assume maximal_marginal_relevance method accepts embeddings and + # assume maximal_marginal_relevance method accepts embeddings and # scores, and returns indices of selected docs mmr_selected_indices = maximal_marginal_relevance( np.array(embedding, dtype=np.float32), @@ -765,7 +765,7 @@ def mmr_from_docs_embeddings( lambda_mult=lambda_mult, ) - # Filter documents based on MMR-selected indices and map scores + # filter documents based on MMR-selected indices and map scores mmr_selected_documents_with_scores = [ (documents[i], scores[i]) for i in mmr_selected_indices ] @@ -971,9 +971,9 @@ def _initialize( self.json_insert_mode = "json" self.json_type = oracledb.DB_TYPE_JSON - """Initialize with oracledb client.""" + # initialize with oracledb client. self.client = client - """Initialize with necessary components.""" + # initialize with necessary components. if not isinstance(embedding_function, Embeddings): logger.warning( "`embedding_function` is expected to be an Embeddings " @@ -1003,21 +1003,21 @@ def embeddings(self) -> Optional[Embeddings]: ) def get_embedding_dimension(self) -> int: - # Embed the single document by wrapping it in a list + # embed the single document by wrapping it in a list embedded_document = self._embed_documents( [self.query if self.query is not None else ""] ) - # Get the first (and only) embedding's dimension + # get the first (and only) embedding's dimension return len(embedded_document[0]) async def aget_embedding_dimension(self) -> int: - # Embed the single document by wrapping it in a list + # embed the single document by wrapping it in a list embedded_document = await self._aembed_documents( [self.query if self.query is not None else ""] ) - # Get the first (and only) embedding's dimension + # get the first (and only) embedding's dimension return len(embedded_document[0]) def _embed_documents(self, texts: List[str]) -> List[List[float]]: @@ -1293,7 +1293,7 @@ def _get_clob_value(self, result: Any) -> str: if isinstance(raw_data, bytes): clob_value = raw_data.decode( "utf-8" - ) # Specify the correct encoding + ) # specify the correct encoding else: clob_value = raw_data elif isinstance(result, str): @@ -1311,7 +1311,7 @@ async def _aget_clob_value(self, result: Any) -> str: if isinstance(raw_data, bytes): clob_value = raw_data.decode( "utf-8" - ) # Specify the correct encoding + ) # specify the correct encoding else: clob_value = raw_data elif isinstance(result, str): @@ -1345,7 +1345,7 @@ def similarity_search_by_vector_with_relevance_scores( return_embeddings=False, ) - # Execute the query + # execute the query connection = _get_connection(self.client) if connection is None: raise ValueError("Failed to acquire a connection.") @@ -1353,7 +1353,7 @@ def similarity_search_by_vector_with_relevance_scores( cursor.execute(query, embedding=embedding_arr) results = cursor.fetchall() - # Filter results if filter is provided + # filter results if filter is provided for result in results: metadata = result[2] or {} @@ -1365,7 +1365,7 @@ def similarity_search_by_vector_with_relevance_scores( ) distance = result[3] - # Apply filtering based on the 'filter' dictionary + # apply filtering based on the 'filter' dictionary if not filter or all( metadata.get(key) in value for key, value in filter.items() ): @@ -1399,12 +1399,12 @@ async def asimilarity_search_by_vector_with_relevance_scores( ) async def context(connection: Any) -> List: - # Execute the query + # execute the query with connection.cursor() as cursor: await cursor.execute(query, embedding=embedding_arr) results = await cursor.fetchall() - # Filter results if filter is provided + # filter results if filter is provided for result in results: metadata = result[2] or {} @@ -1418,7 +1418,7 @@ async def context(connection: Any) -> List: ) distance = result[3] - # Apply filtering based on the 'filter' dictionary + # apply filtering based on the 'filter' dictionary if not filter or all( metadata.get(key) in value for key, value in filter.items() ): @@ -1453,7 +1453,7 @@ def similarity_search_by_vector_returning_embeddings( return_embeddings=True, ) - # Execute the query + # execute the query connection = _get_connection(self.client) if connection is None: raise ValueError("Failed to acquire a connection.") @@ -1465,7 +1465,7 @@ def similarity_search_by_vector_returning_embeddings( page_content_str = self._get_clob_value(result[1]) metadata = result[2] or {} - # Apply filter if provided and matches; otherwise, add all + # apply filter if provided and matches; otherwise, add all # documents if not filter or all( metadata.get(key) in value for key, value in filter.items() @@ -1475,7 +1475,7 @@ def similarity_search_by_vector_returning_embeddings( ) distance = result[3] - # Assuming result[4] is already in the correct format; + # assuming result[4] is already in the correct format; # adjust if necessary current_embedding = ( np.array(result[4], dtype=np.float32) @@ -1513,7 +1513,7 @@ async def asimilarity_search_by_vector_returning_embeddings( ) async def context(connection: Any) -> List: - # Execute the query + # execute the query with connection.cursor() as cursor: await cursor.execute(query, embedding=embedding_arr) results = await cursor.fetchall() @@ -1522,7 +1522,7 @@ async def context(connection: Any) -> List: page_content_str = await self._aget_clob_value(result[1]) metadata = result[2] or {} - # Apply filter if provided and matches; otherwise, add all + # apply filter if provided and matches; otherwise, add all # documents if not filter or all( metadata.get(key) in value for key, value in filter.items() @@ -1532,7 +1532,7 @@ async def context(connection: Any) -> List: ) distance = result[3] - # Assuming result[4] is already in the correct format; + # assuming result[4] is already in the correct format; # adjust if necessary current_embedding = ( np.array(result[4], dtype=np.float32) @@ -1582,11 +1582,11 @@ def max_marginal_relevance_search_with_score_by_vector( relevance and score for each. """ - # Fetch documents and their scores + # fetch documents and their scores docs_scores_embeddings = self.similarity_search_by_vector_returning_embeddings( embedding, fetch_k, filter=filter ) - # Assuming documents_with_scores is a list of tuples (Document, score) + # assuming documents_with_scores is a list of tuples (Document, score) mmr_selected_documents_with_scores = mmr_from_docs_embeddings( docs_scores_embeddings, embedding, k, lambda_mult ) @@ -1629,13 +1629,13 @@ async def amax_marginal_relevance_search_with_score_by_vector( relevance and score for each. """ - # Fetch documents and their scores + # fetch documents and their scores docs_scores_embeddings = ( await self.asimilarity_search_by_vector_returning_embeddings( embedding, fetch_k, filter=filter ) ) - # Assuming documents_with_scores is a list of tuples (Document, score) + # assuming documents_with_scores is a list of tuples (Document, score) mmr_selected_documents_with_scores = mmr_from_docs_embeddings( docs_scores_embeddings, embedding, k, lambda_mult ) From 01471b751d6679bbc264b3180296713085e68804 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Tue, 22 Jul 2025 15:33:15 +0000 Subject: [PATCH 06/12] Fix error handling and version check --- .../vectorstores/oraclevs.py | 312 ++++++++---------- .../vectorstores/test_oraclevs.py | 190 ++++++++++- 2 files changed, 310 insertions(+), 192 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index 7a46f43..d1bac53 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -7,7 +7,6 @@ import functools import hashlib import inspect -import json import logging import os import uuid @@ -144,18 +143,53 @@ def _handle_exceptions(func: T) -> T: def wrapper(*args: Any, **kwargs: Any) -> Any: try: return func(*args, **kwargs) - except RuntimeError as db_err: - # handle a known type of error (e.g., DB-related) specifically + except oracledb.Error as db_err: + # Handle a known type of error (e.g., DB-related) specifically logger.exception("DB-related error occurred.") raise RuntimeError( - "Failed due to a DB issue: {}".format(db_err) + "Failed due to a DB error: {}".format(db_err) ) from db_err + except RuntimeError as runtime_err: + # Handle a runtime error + logger.exception("Runtime error occurred.") + raise RuntimeError( + "Failed due to a runtime error: {}".format(runtime_err) + ) from runtime_err + except ValueError as val_err: + # Handle another known type of error specifically + logger.exception("Validation error.") + raise ValueError("Validation failed: {}".format(val_err)) from val_err + except Exception as e: + # Generic handler for all other exceptions + logger.exception("An unexpected error occurred: {}".format(e)) + raise RuntimeError("Unexpected error: {}".format(e)) from e + + return cast(T, wrapper) + + +def _ahandle_exceptions(func: T) -> T: + @functools.wraps(func) + async def wrapper(*args: Any, **kwargs: Any) -> Any: + try: + return await func(*args, **kwargs) + except oracledb.Error as db_err: + # Handle a known type of error (e.g., DB-related) specifically + logger.exception("DB-related error occurred.") + raise RuntimeError( + "Failed due to a DB error: {}".format(db_err) + ) from db_err + except RuntimeError as runtime_err: + # Handle a runtime error + logger.exception("Runtime error occurred.") + raise RuntimeError( + "Failed due to a runtime error: {}".format(runtime_err) + ) from runtime_err except ValueError as val_err: - # handle another known type of error specifically + # Handle another known type of error specifically logger.exception("Validation error.") raise ValueError("Validation failed: {}".format(val_err)) from val_err except Exception as e: - # generic handler for all other exceptions + # Generic handler for all other exceptions logger.exception("An unexpected error occurred: {}".format(e)) raise RuntimeError("Unexpected error: {}".format(e)) from e @@ -165,7 +199,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: def _table_exists(connection: Connection, table_name: str) -> bool: try: with connection.cursor() as cursor: - cursor.execute(f"select 1 from {table_name} where ROWNUM < 1") + cursor.execute(f"SELECT 1 FROM {table_name} WHERE ROWNUM < 1") return True except oracledb.DatabaseError as ex: err_obj = ex.args @@ -177,7 +211,7 @@ def _table_exists(connection: Connection, table_name: str) -> bool: async def _atable_exists(connection: AsyncConnection, table_name: str) -> bool: try: with connection.cursor() as cursor: - await cursor.execute(f"select 1 from {table_name} where ROWNUM < 1") + await cursor.execute(f"SELECT 1 FROM {table_name} WHERE ROWNUM < 1") return True except oracledb.DatabaseError as ex: err_obj = ex.args @@ -186,52 +220,65 @@ async def _atable_exists(connection: AsyncConnection, table_name: str) -> bool: raise -def _compare_version(version: str, target_version: str) -> bool: - # split both version strings into parts - version_parts = [int(part) for part in version.split(".")] - target_parts = [int(part) for part in target_version.split(".")] - - # compare each part - for v, t in zip(version_parts, target_parts): - if v < t: - return True # current version is less - elif v > t: - return False # current version is greater - - # if all parts equal so far, check if version has fewer parts than target_version - return len(version_parts) < len(target_parts) +def _normalize_oracle_identifier(name: str) -> str: + name = name.strip() + if name.startswith('"') and name.endswith('"'): + return name + else: + return name.upper() @_handle_exceptions -def _index_exists(connection: Connection, index_name: str) -> bool: +def _index_exists( + connection: Connection, index_name: str, table_name: Optional[str] = None +) -> bool: # check if the index exists - query = """ + query = f""" SELECT index_name FROM all_indexes - WHERE upper(index_name) = upper(:idx_name) + WHERE index_name = :idx_name + {"AND table_name = :table_name" if table_name else ""} """ with connection.cursor() as cursor: # execute the query - cursor.execute(query, idx_name=index_name.upper()) + if table_name: + cursor.execute( + query, + idx_name=_normalize_oracle_identifier(index_name), + table_name=_normalize_oracle_identifier(table_name), + ) + else: + cursor.execute(query, idx_name=_normalize_oracle_identifier(index_name)) result = cursor.fetchone() # check if the index exists return result is not None -@_handle_exceptions -async def _aindex_exists(connection: AsyncConnection, index_name: str) -> bool: +async def _aindex_exists( + connection: AsyncConnection, index_name: str, table_name: Optional[str] = None +) -> bool: # check if the index exists - query = """ - SELECT index_name + query = f""" + SELECT index_name, table_name FROM all_indexes - WHERE upper(index_name) = upper(:idx_name) + WHERE index_name = :idx_name + {"AND table_name = :table_name" if table_name else ""} """ with connection.cursor() as cursor: # execute the query - await cursor.execute(query, idx_name=index_name.upper()) + if table_name: + await cursor.execute( + query, + idx_name=_normalize_oracle_identifier(index_name), + table_name=_normalize_oracle_identifier(table_name), + ) + else: + await cursor.execute( + query, idx_name=_normalize_oracle_identifier(index_name) + ) result = await cursor.fetchone() # check if the index exists @@ -286,7 +333,6 @@ def _create_table(connection: Connection, table_name: str, embedding_dim: int) - logger.info("Table already exists...") -@_handle_exceptions async def _acreate_table( connection: AsyncConnection, table_name: str, embedding_dim: int ) -> None: @@ -428,7 +474,7 @@ def _create_hnsw_index( idx_name, ddl = _get_hnsw_index_ddl(table_name, distance_strategy, params) # check if the index exists - if not _index_exists(connection, idx_name): + if not _index_exists(connection, idx_name, table_name): with connection.cursor() as cursor: cursor.execute(ddl) logger.info("Index created successfully...") @@ -510,7 +556,7 @@ def _create_ivf_index( idx_name, ddl = _get_ivf_index_ddl(table_name, distance_strategy, params) # check if the index exists - if not _index_exists(connection, idx_name): + if not _index_exists(connection, idx_name, table_name): with connection.cursor() as cursor: cursor.execute(ddl) logger.info("Index created successfully...") @@ -518,7 +564,7 @@ def _create_ivf_index( logger.info("Index already exists...") -@_handle_exceptions +@_ahandle_exceptions async def acreate_index( client: Any, vector_store: OracleVS, @@ -560,7 +606,6 @@ async def context(connection: Any) -> None: return -@_handle_exceptions async def _acreate_hnsw_index( connection: AsyncConnection, table_name: str, @@ -570,7 +615,7 @@ async def _acreate_hnsw_index( idx_name, ddl = _get_hnsw_index_ddl(table_name, distance_strategy, params) # check if the index exists - if not await _aindex_exists(connection, idx_name): + if not await _aindex_exists(connection, idx_name, table_name): with connection.cursor() as cursor: await cursor.execute(ddl) logger.info("Index created successfully...") @@ -578,7 +623,6 @@ async def _acreate_hnsw_index( logger.info("Index already exists...") -@_handle_exceptions async def _acreate_ivf_index( connection: AsyncConnection, table_name: str, @@ -588,7 +632,7 @@ async def _acreate_ivf_index( idx_name, ddl = _get_ivf_index_ddl(table_name, distance_strategy, params) # check if the index exists - if not await _aindex_exists(connection, idx_name): + if not await _aindex_exists(connection, idx_name, table_name): with connection.cursor() as cursor: await cursor.execute(ddl) logger.info("Index created successfully...") @@ -601,7 +645,7 @@ def drop_table_purge(client: Any, table_name: str) -> None: """Drop a table and purge it from the database. Args: - client: The OracleDB connection object. + client: oracledb connection object. table_name: The name of the table to drop. Raises: @@ -620,12 +664,12 @@ def drop_table_purge(client: Any, table_name: str) -> None: return -@_handle_exceptions +@_ahandle_exceptions async def adrop_table_purge(client: Any, table_name: str) -> None: """Drop a table and purge it from the database. Args: - client: The OracleDB connection object. + client: oracledb connection object. table_name: The name of the table to drop. Raises: @@ -669,7 +713,7 @@ def drop_index_if_exists(client: Any, index_name: str) -> None: return -@_handle_exceptions +@_ahandle_exceptions async def adrop_index_if_exists(client: Any, index_name: str) -> None: """Drop an index if it exists. @@ -800,7 +844,6 @@ def _get_similarity_search_query( return query -@_handle_exceptions async def _handle_context( client: Any, context: Callable[ @@ -881,7 +924,7 @@ def __init__( _create_table(connection, table_name, embedding_dim) @classmethod - @_handle_exceptions + @_ahandle_exceptions async def acreate( cls, client: Any, @@ -931,45 +974,20 @@ def _initialize( query: Optional[str] = "What is a Oracle database", params: Optional[Dict[str, Any]] = None, ) -> None: - self.insert_mode = "array" - - if hasattr(connection, "thin") and connection.thin: - if oracledb.__version__ == "2.1.0": + if not (hasattr(connection, "thin") and connection.thin): + if oracledb.clientversion()[:2] < (23, 4): raise Exception( - "Oracle DB python thin client driver version 2.1.0 not supported" - ) - elif _compare_version(oracledb.__version__, "2.2.0"): - self.insert_mode = "clob" - else: - self.insert_mode = "array" - else: - if (_compare_version(oracledb.__version__, "2.1.0")) and ( - not ( - _compare_version( - ".".join(map(str, oracledb.clientversion())), "23.4" - ) - ) - ): - raise Exception( - "Oracle DB python thick client driver version earlier than " - "2.1.0 not supported with client libraries greater than " - "equal to 23.4" + f"Oracle DB client driver version {oracledb.clientversion()} not \ + supported, must be >=23.4 for vector support" ) - if _compare_version(".".join(map(str, oracledb.clientversion())), "23.4"): - self.insert_mode = "clob" - else: - self.insert_mode = "array" + db_version = tuple([int(v) for v in connection.version.split(".")]) - if _compare_version(oracledb.__version__, "2.1.0"): - self.insert_mode = "clob" - - self.json_insert_mode = "clob" - if ( - hasattr(connection, "thin") and connection.thin - ) or oracledb.clientversion()[0] >= 21: - self.json_insert_mode = "json" - self.json_type = oracledb.DB_TYPE_JSON + if db_version < (23, 4): + raise Exception( + f"Oracle DB version {oracledb.__version__} not supported, \ + must be >=23.4 for vector support" + ) # initialize with oracledb client. self.client = client @@ -1080,42 +1098,23 @@ def add_texts( if not metadatas: metadatas = [{} for _ in texts] - docs: List[Tuple[Any, Any, Any, Any]] - if self.insert_mode == "clob": - docs = [ - ( - id_, - json.dumps(embedding), - json.dumps(metadata) - if self.json_insert_mode != "json" - else metadata, - text, - ) - for id_, embedding, metadata, text in zip( - processed_ids, embeddings, metadatas, texts - ) - ] - else: - docs = [ - ( - id_, - array.array("f", embedding), - json.dumps(metadata) - if self.json_insert_mode != "json" - else metadata, - text, - ) - for id_, embedding, metadata, text in zip( - processed_ids, embeddings, metadatas, texts - ) - ] + docs: List[Tuple[Any, Any, Any, Any]] = [ + ( + id_, + array.array("f", embedding), + metadata, + text, + ) + for id_, embedding, metadata, text in zip( + processed_ids, embeddings, metadatas, texts + ) + ] connection = _get_connection(self.client) if connection is None: raise ValueError("Failed to acquire a connection.") with connection.cursor() as cursor: - if self.json_insert_mode == "json": - cursor.setinputsizes(None, None, self.json_type, None) + cursor.setinputsizes(None, None, oracledb.DB_TYPE_JSON, None) cursor.executemany( f"INSERT INTO {self.table_name} (id, embedding, metadata, " f"text) VALUES (:1, :2, :3, :4)", @@ -1124,7 +1123,7 @@ def add_texts( connection.commit() return processed_ids - @_handle_exceptions + @_ahandle_exceptions async def aadd_texts( self, texts: Iterable[str], @@ -1149,42 +1148,23 @@ async def aadd_texts( if not metadatas: metadatas = [{} for _ in texts] - docs: List[Tuple[Any, Any, Any, Any]] - if self.insert_mode == "clob": - docs = [ - ( - id_, - json.dumps(embedding), - json.dumps(metadata) - if self.json_insert_mode != "json" - else metadata, - text, - ) - for id_, embedding, metadata, text in zip( - processed_ids, embeddings, metadatas, texts - ) - ] - else: - docs = [ - ( - id_, - array.array("f", embedding), - json.dumps(metadata) - if self.json_insert_mode != "json" - else metadata, - text, - ) - for id_, embedding, metadata, text in zip( - processed_ids, embeddings, metadatas, texts - ) - ] + docs: List[Tuple[Any, Any, Any, Any]] = [ + ( + id_, + array.array("f", embedding), + metadata, + text, + ) + for id_, embedding, metadata, text in zip( + processed_ids, embeddings, metadatas, texts + ) + ] async def context(connection: Any) -> None: if connection is None: raise ValueError("Failed to acquire a connection.") with connection.cursor() as cursor: - if self.json_insert_mode == "json": - cursor.setinputsizes(None, None, self.json_type, None) + cursor.setinputsizes(None, None, oracledb.DB_TYPE_JSON, None) await cursor.executemany( f"INSERT INTO {self.table_name} (id, embedding, metadata, " f"text) VALUES (:1, :2, :3, :4)", @@ -1284,7 +1264,6 @@ async def asimilarity_search_with_score( ) return docs_and_scores - @_handle_exceptions def _get_clob_value(self, result: Any) -> str: clob_value = "" if result: @@ -1302,7 +1281,6 @@ def _get_clob_value(self, result: Any) -> str: raise Exception("Unexpected type:", type(result)) return clob_value - @_handle_exceptions async def _aget_clob_value(self, result: Any) -> str: clob_value = "" if result: @@ -1330,11 +1308,7 @@ def similarity_search_by_vector_with_relevance_scores( ) -> List[Tuple[Document, float]]: docs_and_scores = [] - embedding_arr: Any - if self.insert_mode == "clob": - embedding_arr = json.dumps(embedding) - else: - embedding_arr = array.array("f", embedding) + embedding_arr: Any = array.array("f", embedding) db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) query = _get_similarity_search_query( @@ -1373,7 +1347,7 @@ def similarity_search_by_vector_with_relevance_scores( return docs_and_scores - @_handle_exceptions + @_ahandle_exceptions async def asimilarity_search_by_vector_with_relevance_scores( self, embedding: List[float], @@ -1383,11 +1357,7 @@ async def asimilarity_search_by_vector_with_relevance_scores( ) -> List[Tuple[Document, float]]: docs_and_scores = [] - embedding_arr: Any - if self.insert_mode == "clob": - embedding_arr = json.dumps(embedding) - else: - embedding_arr = array.array("f", embedding) + embedding_arr: Any = array.array("f", embedding) db_filter: Optional[FilterGroup] = kwargs.get("db_filter", None) query = _get_similarity_search_query( @@ -1436,11 +1406,7 @@ def similarity_search_by_vector_returning_embeddings( filter: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> List[Tuple[Document, float, NDArray[np.float32]]]: - embedding_arr: Any - if self.insert_mode == "clob": - embedding_arr = json.dumps(embedding) - else: - embedding_arr = array.array("f", embedding) + embedding_arr: Any = array.array("f", embedding) documents = [] @@ -1487,7 +1453,7 @@ def similarity_search_by_vector_returning_embeddings( return documents - @_handle_exceptions + @_ahandle_exceptions async def asimilarity_search_by_vector_returning_embeddings( self, embedding: List[float], @@ -1495,11 +1461,7 @@ async def asimilarity_search_by_vector_returning_embeddings( filter: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> List[Tuple[Document, float, NDArray[np.float32]]]: - embedding_arr: Any - if self.insert_mode == "clob": - embedding_arr = json.dumps(embedding) - else: - embedding_arr = array.array("f", embedding) + embedding_arr: Any = array.array("f", embedding) documents = [] @@ -1546,7 +1508,6 @@ async def context(connection: Any) -> List: return await _handle_context(self.client, context) - @_handle_exceptions def max_marginal_relevance_search_with_score_by_vector( self, embedding: List[float], @@ -1593,7 +1554,6 @@ def max_marginal_relevance_search_with_score_by_vector( return mmr_selected_documents_with_scores - @_handle_exceptions async def amax_marginal_relevance_search_with_score_by_vector( self, embedding: List[float], @@ -1642,7 +1602,6 @@ async def amax_marginal_relevance_search_with_score_by_vector( return mmr_selected_documents_with_scores - @_handle_exceptions def max_marginal_relevance_search_by_vector( self, embedding: List[float], @@ -1677,7 +1636,6 @@ def max_marginal_relevance_search_by_vector( ) return [doc for doc, _ in docs_and_scores] - @_handle_exceptions async def amax_marginal_relevance_search_by_vector( self, embedding: List[float], @@ -1758,7 +1716,7 @@ def max_marginal_relevance_search( ) return documents - @_handle_exceptions + @_ahandle_exceptions async def amax_marginal_relevance_search( self, query: str, @@ -1820,7 +1778,7 @@ def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: cursor.execute(ddl, bind_vars) connection.commit() - @_handle_exceptions + @_ahandle_exceptions async def adelete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> None: """Delete by vector IDs. Args: @@ -1883,8 +1841,6 @@ def from_texts( params, ) = OracleVS._from_texts_helper(texts, embedding, metadatas, **kwargs) - drop_table_purge(client, table_name) - vss = cls( client=client, embedding_function=embedding, @@ -1898,7 +1854,7 @@ def from_texts( return vss @classmethod - @_handle_exceptions + @_ahandle_exceptions async def afrom_texts( cls: Type[OracleVS], texts: Iterable[str], @@ -1914,8 +1870,6 @@ async def afrom_texts( params, ) = OracleVS._from_texts_helper(texts, embedding, metadatas, **kwargs) - await adrop_table_purge(client, table_name) - vss = await OracleVS.acreate( client=client, embedding_function=embedding, diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py index 9e3de66..b127607 100644 --- a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -2,6 +2,8 @@ # import required modules import asyncio +import logging +import os import sys import threading @@ -747,7 +749,7 @@ async def test_create_hnsw_index_test_async() -> None: # 5. idx_name passed empty # Expectation:ORA-01741: illegal zero-length identifier - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -826,7 +828,7 @@ async def test_create_hnsw_index_test_async() -> None: await adrop_index_if_exists(connection, "idx11") await adrop_table_purge(connection, "TB9") # index not created: - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -844,7 +846,7 @@ async def test_create_hnsw_index_test_async() -> None: await adrop_index_if_exists(connection, "idx11") # index not created: - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB11", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -861,7 +863,7 @@ async def test_create_hnsw_index_test_async() -> None: ) await adrop_index_if_exists(connection, "idx11") # index not created - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB12", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -878,7 +880,7 @@ async def test_create_hnsw_index_test_async() -> None: ) await adrop_index_if_exists(connection, "idx11") # index not created - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB13", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -897,7 +899,7 @@ async def test_create_hnsw_index_test_async() -> None: await adrop_index_if_exists(connection, "idx11") # with negative values/out-of-bound values for all 4 of them, we get the same errors # Expectation:Index not created - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB14", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -942,7 +944,7 @@ async def test_create_hnsw_index_test_async() -> None: # 11. index_name as # Expectation:U1 not present - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "U1.TB16", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -963,7 +965,7 @@ async def test_create_hnsw_index_test_async() -> None: # 12. Index_name size >129 # Expectation:Index not created - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs = await OracleVS.acreate( connection, model1, "TB17", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -1403,7 +1405,7 @@ async def add1(val: str) -> None: ids10 = texts await vs_obj.aadd_texts(texts, ids=ids10) - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): task_1 = asyncio.create_task(add1("Sri Ram")) task_2 = asyncio.create_task(add1("Sri Ram")) await asyncio.gather(task_1, task_2) @@ -1412,7 +1414,7 @@ async def add1(val: str) -> None: # 8. create object with table name of type # Expectation:U1 does not exist - with pytest.raises(oracledb.Error): + with pytest.raises(RuntimeError): vs_obj = await OracleVS.acreate( connection, model, "U1.TB14", DistanceStrategy.DOT_PRODUCT ) @@ -2083,7 +2085,7 @@ async def test_db_filter_test_async() -> None: @pytest.mark.asyncio -async def atest_add_texts_pool_test() -> None: +async def test_add_texts_pool_test() -> None: POOLS_MAX = 4 try: @@ -2278,8 +2280,6 @@ async def test_from_texts_lobs_async() -> None: distance_strategy=DistanceStrategy.COSINE, ) - await acreate_index(connection, vs, {"idx_type": "HNSW", "idx_name": "IDX1"}) - query = "What is a tablespace?" # 1. Test when oracledb.defaults.fetch_lobs is set to False @@ -2293,3 +2293,167 @@ async def test_from_texts_lobs_async() -> None: await adrop_table_purge(connection, "TB10") oracledb.defaults.fetch_lobs = True + + +################################## +##### test_index_table_case ##### +################################## + + +def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: + try: + connection = oracledb.connect(user=username, password=password, dsn=dsn) + except Exception: + sys.exit(1) + + drop_table_purge(connection, "TB1") + drop_table_purge(connection, "TB2") + + # LOGGER = logging.getLogger(__name__) + + texts = ["Rohan", "Shailendra"] + metadata = [ + {"id": "100", "link": "Document Example Test 1"}, + {"id": "101", "link": "Document Example Test 2"}, + ] + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + + with caplog.at_level(logging.INFO): + vs_obj = OracleVS(connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) + + assert "Table created successfully..." in caplog.text + + with caplog.at_level(logging.INFO): + OracleVS(connection, model, "Tb1", DistanceStrategy.EUCLIDEAN_DISTANCE) + + assert "Table already exists..." in caplog.text + + with caplog.at_level(logging.INFO): + vs_obj2 = OracleVS( + connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + + assert "Table created successfully..." in caplog.text + + vs_obj.add_texts(texts, metadata) + + with caplog.at_level(logging.INFO): + create_index( + connection, vs_obj, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} + ) + + assert "Index created successfully..." in caplog.text + + with caplog.at_level(logging.INFO): + create_index( + connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + ) + + assert "Index already exists..." in caplog.text + + with pytest.raises( + RuntimeError, match="name is already used by an existing object" + ): + create_index( + connection, vs_obj2, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + ) + + drop_index_if_exists(connection, "hnsw_idx2") + + with caplog.at_level(logging.INFO): + create_index( + connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} + ) + + assert "Index created successfully..." in caplog.text + + with pytest.raises(RuntimeError, match="such column list already indexed"): + create_index( + connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + ) + + drop_table_purge(connection, "TB1") + drop_table_purge(connection, "TB2") + + +@pytest.mark.asyncio +async def test_insdex_table_case_async(caplog: pytest.LogCaptureFixture) -> None: + try: + connection = await oracledb.connect_async( + user=username, password=password, dsn=dsn + ) + except Exception: + sys.exit(1) + + await adrop_table_purge(connection, "TB1") + await adrop_table_purge(connection, "TB2") + + # LOGGER = logging.getLogger(__name__) + + texts = ["Rohan", "Shailendra"] + metadata = [ + {"id": "100", "link": "Document Example Test 1"}, + {"id": "101", "link": "Document Example Test 2"}, + ] + model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") + + with caplog.at_level(logging.INFO): + vs_obj = await OracleVS.acreate( + connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + + assert "Table created successfully..." in caplog.text + + with caplog.at_level(logging.INFO): + await OracleVS.acreate( + connection, model, "Tb1", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + + assert "Table already exists..." in caplog.text + + with caplog.at_level(logging.INFO): + vs_obj2 = await OracleVS.acreate( + connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE + ) + + assert "Table created successfully..." in caplog.text + + await vs_obj.aadd_texts(texts, metadata) + + with caplog.at_level(logging.INFO): + await acreate_index( + connection, vs_obj, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} + ) + + assert "Index created successfully..." in caplog.text + + with caplog.at_level(logging.INFO): + await acreate_index( + connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + ) + + assert "Index already exists..." in caplog.text + + with pytest.raises( + RuntimeError, match="name is already used by an existing object" + ): + await acreate_index( + connection, vs_obj2, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + ) + + await adrop_index_if_exists(connection, "hnsw_idx2") + + with caplog.at_level(logging.INFO): + await acreate_index( + connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} + ) + + assert "Index created successfully..." in caplog.text + + with pytest.raises(RuntimeError, match="such column list already indexed"): + await acreate_index( + connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + ) + + await adrop_table_purge(connection, "TB1") + await adrop_table_purge(connection, "TB2") From 95f31dc712ae0cd0be139fcbc8e6997e63ba77f4 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Wed, 23 Jul 2025 08:18:19 +0000 Subject: [PATCH 07/12] Add file level comments - better logging --- .../document_loaders/oracleadb_loader.py | 8 +++- .../document_loaders/oracleai.py | 18 +++++---- .../langchain_oracledb/embeddings/oracleai.py | 17 ++++---- .../langchain_oracledb/utilities/oracleai.py | 16 ++++---- .../vectorstores/oraclevs.py | 39 +++++++++++-------- libs/oracledb/tests/__init__.py | 2 +- .../document_loaders/test_oracleds.py | 16 +++++--- .../vectorstores/test_oraclevs.py | 36 ++++++++++------- .../document_loaders/test_oracleadb.py | 7 ++++ 9 files changed, 95 insertions(+), 64 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py index c1b9917..3d26d38 100644 --- a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py @@ -1,5 +1,11 @@ -# Copyright (c) 2025 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +oracleadb_loader.py + +Contains OracleAutonomousDatabaseLoader for connecting to +Oracle Autonomous Database (ADB). +""" from typing import Any, Dict, List, Optional, Union diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py index e03d7d4..2d55ad3 100644 --- a/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleai.py @@ -1,13 +1,15 @@ -# Copyright (c) 2025 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +oracleai.py -# Authors: -# Harichandan Roy (hroy) -# David Jiang (ddjiang) -# -# ----------------------------------------------------------------------------- -# oracleai.py -# ----------------------------------------------------------------------------- +Defines OracleDocLoader and OracleTextSplitter for loading +and splitting documents using Oracle AI Vector Search. + +Authors: + - Harichandan Roy (hroy) + - David Jiang (ddjiang) +""" from __future__ import annotations diff --git a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py index 501dbaf..13d7ec1 100644 --- a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py +++ b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py @@ -1,14 +1,15 @@ -# Copyright (c) 2025 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +oracleai.py -# Authors: -# Harichandan Roy (hroy) -# David Jiang (ddjiang) -# -# ----------------------------------------------------------------------------- -# oracleai.py -# ----------------------------------------------------------------------------- +Implements OracleEmbeddings for generating and handling +vector embeddings with Oracle AI Vector Search. +Authors: + - Harichandan Roy (hroy) + - David Jiang (ddjiang) +""" from __future__ import annotations import json diff --git a/libs/oracledb/langchain_oracledb/utilities/oracleai.py b/libs/oracledb/langchain_oracledb/utilities/oracleai.py index 83ed40f..2fcfe17 100644 --- a/libs/oracledb/langchain_oracledb/utilities/oracleai.py +++ b/libs/oracledb/langchain_oracledb/utilities/oracleai.py @@ -1,14 +1,14 @@ -# Copyright (c) 2025 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +oracleai.py -# Authors: -# Harichandan Roy (hroy) -# David Jiang (ddjiang) -# -# ----------------------------------------------------------------------------- -# oracleai.py -# ----------------------------------------------------------------------------- +Implements OracleSummary with Oracle AI Vector Search support. +Authors: + - Harichandan Roy (hroy) + - David Jiang (ddjiang) +""" from __future__ import annotations import json diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index d1bac53..e473f39 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -1,6 +1,11 @@ -# Copyright (c) 2025 Oracle and/or its affiliates. +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +oraclevs.py +Provides integration between Oracle Vector Database and +LangChain for vector storage and search. +""" from __future__ import annotations import array @@ -328,9 +333,9 @@ def _create_table(connection: Connection, table_name: str, embedding_dim: int) - ) ddl = f"CREATE TABLE {table_name} ({ddl_body})" cursor.execute(ddl) - logger.info("Table created successfully...") + logger.info(f"Table {table_name} created successfully...") else: - logger.info("Table already exists...") + logger.info(f"Table {table_name} already exists...") async def _acreate_table( @@ -345,9 +350,9 @@ async def _acreate_table( ) ddl = f"CREATE TABLE {table_name} ({ddl_body})" await cursor.execute(ddl) - logger.info("Table created successfully...") + logger.info(f"Table {table_name} created successfully...") else: - logger.info("Table already exists...") + logger.info(f"Table {table_name} already exists...") @_handle_exceptions @@ -477,9 +482,9 @@ def _create_hnsw_index( if not _index_exists(connection, idx_name, table_name): with connection.cursor() as cursor: cursor.execute(ddl) - logger.info("Index created successfully...") + logger.info(f"Index {idx_name} created successfully...") else: - logger.info("Index already exists...") + logger.info(f"Index {idx_name} already exists...") def _get_ivf_index_ddl( @@ -559,9 +564,9 @@ def _create_ivf_index( if not _index_exists(connection, idx_name, table_name): with connection.cursor() as cursor: cursor.execute(ddl) - logger.info("Index created successfully...") + logger.info(f"Index {idx_name} created successfully...") else: - logger.info("Index already exists...") + logger.info(f"Index {idx_name} already exists...") @_ahandle_exceptions @@ -618,9 +623,9 @@ async def _acreate_hnsw_index( if not await _aindex_exists(connection, idx_name, table_name): with connection.cursor() as cursor: await cursor.execute(ddl) - logger.info("Index created successfully...") + logger.info(f"Index {idx_name} created successfully...") else: - logger.info("Index already exists...") + logger.info(f"Index {idx_name} already exists...") async def _acreate_ivf_index( @@ -635,9 +640,9 @@ async def _acreate_ivf_index( if not await _aindex_exists(connection, idx_name, table_name): with connection.cursor() as cursor: await cursor.execute(ddl) - logger.info("Index created successfully...") + logger.info(f"Index {idx_name} created successfully...") else: - logger.info("Index already exists...") + logger.info(f"Index {idx_name} already exists...") @_handle_exceptions @@ -658,9 +663,9 @@ def drop_table_purge(client: Any, table_name: str) -> None: with connection.cursor() as cursor: ddl = f"DROP TABLE {table_name} PURGE" cursor.execute(ddl) - logger.info("Table dropped successfully...") + logger.info(f"Table {table_name} dropped successfully...") else: - logger.info("Table not found...") + logger.info(f"Table {table_name} not found...") return @@ -681,9 +686,9 @@ async def context(connection: Any) -> None: with connection.cursor() as cursor: ddl = f"DROP TABLE {table_name} PURGE" await cursor.execute(ddl) - logger.info("Table dropped successfully...") + logger.info(f"Table {table_name} dropped successfully...") else: - logger.info("Table not found...") + logger.info(f"Table {table_name} not found...") await _handle_context(client, context) return diff --git a/libs/oracledb/tests/__init__.py b/libs/oracledb/tests/__init__.py index 09c1843..9d558f0 100644 --- a/libs/oracledb/tests/__init__.py +++ b/libs/oracledb/tests/__init__.py @@ -1,2 +1,2 @@ -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ diff --git a/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py b/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py index 0cf4350..9697f49 100644 --- a/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py +++ b/libs/oracledb/tests/integration_tests/document_loaders/test_oracleds.py @@ -1,9 +1,13 @@ -# Authors: -# Sudhir Kumar (sudhirkk) -# -# ----------------------------------------------------------------------------- -# test_oracleds.py -# ----------------------------------------------------------------------------- +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +test_oracleds.py + +Unit tests for OracleDocLoader and OracleTextSplitter. + +Authors: + - Sudhir Kumar (sudhirkk) +""" import sys from langchain_oracledb.document_loaders.oracleai import ( diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py index b127607..2399d7e 100644 --- a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -1,9 +1,15 @@ -"""Test Oracle AI Vector Search functionality.""" +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +test_oraclevs.py + +Test Oracle AI Vector Search functionality integration +with OracleVS. +""" # import required modules import asyncio import logging -import os import sys import threading @@ -2321,19 +2327,19 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: with caplog.at_level(logging.INFO): vs_obj = OracleVS(connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) - assert "Table created successfully..." in caplog.text + assert "Table TB1 created successfully..." in caplog.text with caplog.at_level(logging.INFO): OracleVS(connection, model, "Tb1", DistanceStrategy.EUCLIDEAN_DISTANCE) - assert "Table already exists..." in caplog.text + assert "Table Tb1 already exists..." in caplog.text with caplog.at_level(logging.INFO): vs_obj2 = OracleVS( connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table created successfully..." in caplog.text + assert "Table TB2 created successfully..." in caplog.text vs_obj.add_texts(texts, metadata) @@ -2342,14 +2348,14 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: connection, vs_obj, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} ) - assert "Index created successfully..." in caplog.text + assert "Index hnsw_idx2 created successfully..." in caplog.text with caplog.at_level(logging.INFO): create_index( connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} ) - assert "Index already exists..." in caplog.text + assert "Index HNSW_idx2 already exists..." in caplog.text with pytest.raises( RuntimeError, match="name is already used by an existing object" @@ -2365,7 +2371,7 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} ) - assert "Index created successfully..." in caplog.text + assert 'Index "hnsw_idx2" created successfully...' in caplog.text with pytest.raises(RuntimeError, match="such column list already indexed"): create_index( @@ -2377,7 +2383,7 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: @pytest.mark.asyncio -async def test_insdex_table_case_async(caplog: pytest.LogCaptureFixture) -> None: +async def test_index_table_case_async(caplog: pytest.LogCaptureFixture) -> None: try: connection = await oracledb.connect_async( user=username, password=password, dsn=dsn @@ -2402,21 +2408,21 @@ async def test_insdex_table_case_async(caplog: pytest.LogCaptureFixture) -> None connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table created successfully..." in caplog.text + assert "Table TB1 created successfully..." in caplog.text with caplog.at_level(logging.INFO): await OracleVS.acreate( connection, model, "Tb1", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table already exists..." in caplog.text + assert "Table Tb1 already exists..." in caplog.text with caplog.at_level(logging.INFO): vs_obj2 = await OracleVS.acreate( connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table created successfully..." in caplog.text + assert "Table TB2 created successfully..." in caplog.text await vs_obj.aadd_texts(texts, metadata) @@ -2425,14 +2431,14 @@ async def test_insdex_table_case_async(caplog: pytest.LogCaptureFixture) -> None connection, vs_obj, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} ) - assert "Index created successfully..." in caplog.text + assert "Index hnsw_idx2 created successfully..." in caplog.text with caplog.at_level(logging.INFO): await acreate_index( connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} ) - assert "Index already exists..." in caplog.text + assert "Index HNSW_idx2 already exists..." in caplog.text with pytest.raises( RuntimeError, match="name is already used by an existing object" @@ -2448,7 +2454,7 @@ async def test_insdex_table_case_async(caplog: pytest.LogCaptureFixture) -> None connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} ) - assert "Index created successfully..." in caplog.text + assert 'Index "hnsw_idx2" created successfully...' in caplog.text with pytest.raises(RuntimeError, match="such column list already indexed"): await acreate_index( diff --git a/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py b/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py index 34d86f0..bd43fba 100644 --- a/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py +++ b/libs/oracledb/tests/unit_tests/document_loaders/test_oracleadb.py @@ -1,3 +1,10 @@ +# Copyright (c) 2024, 2025 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +test_oracleadb.py + +Unit tests for OracleAutonomousDatabaseLoader. +""" from typing import Dict, List from unittest.mock import MagicMock, patch From 5919e9762ff851ce36c481614ef5cc420c31823f Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 24 Jul 2025 18:33:20 +0000 Subject: [PATCH 08/12] Use LOB outputtypehandler --- .../langchain_oracledb/utilities/oracleai.py | 11 +++- .../vectorstores/oraclevs.py | 61 ++++++------------- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/utilities/oracleai.py b/libs/oracledb/langchain_oracledb/utilities/oracleai.py index 2fcfe17..47a3e9d 100644 --- a/libs/oracledb/langchain_oracledb/utilities/oracleai.py +++ b/libs/oracledb/langchain_oracledb/utilities/oracleai.py @@ -28,6 +28,15 @@ """OracleSummary class""" +def output_type_handler(cursor: Any, metadata: Any) -> Any: + if metadata.type_code is oracledb.DB_TYPE_CLOB: + return cursor.var(oracledb.DB_TYPE_LONG, arraysize=cursor.arraysize) + if metadata.type_code is oracledb.DB_TYPE_BLOB: + return cursor.var(oracledb.DB_TYPE_LONG_RAW, arraysize=cursor.arraysize) + if metadata.type_code is oracledb.DB_TYPE_NCLOB: + return cursor.var(oracledb.DB_TYPE_LONG_NVARCHAR, arraysize=cursor.arraysize) + + class OracleSummary: """Get Summary Args: @@ -57,8 +66,8 @@ def get_summary(self, docs: Any) -> List[str]: results: List[str] = [] try: - oracledb.defaults.fetch_lobs = False cursor = self.conn.cursor() + cursor.outputtypehandler = output_type_handler if self.proxy: cursor.execute( diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index e473f39..46f885c 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -871,6 +871,15 @@ async def _handle_context( return await context(connection) +def output_type_string_handler(cursor: Any, metadata: Any) -> Any: + if metadata.type_code is oracledb.DB_TYPE_CLOB: + return cursor.var(oracledb.DB_TYPE_LONG, arraysize=cursor.arraysize) + if metadata.type_code is oracledb.DB_TYPE_BLOB: + return cursor.var(oracledb.DB_TYPE_LONG_RAW, arraysize=cursor.arraysize) + if metadata.type_code is oracledb.DB_TYPE_NCLOB: + return cursor.var(oracledb.DB_TYPE_LONG_NVARCHAR, arraysize=cursor.arraysize) + + class OracleVS(VectorStore): """`OracleVS` vector store. @@ -1269,40 +1278,6 @@ async def asimilarity_search_with_score( ) return docs_and_scores - def _get_clob_value(self, result: Any) -> str: - clob_value = "" - if result: - if isinstance(result, oracledb.LOB): - raw_data = result.read() - if isinstance(raw_data, bytes): - clob_value = raw_data.decode( - "utf-8" - ) # specify the correct encoding - else: - clob_value = raw_data - elif isinstance(result, str): - clob_value = result - else: - raise Exception("Unexpected type:", type(result)) - return clob_value - - async def _aget_clob_value(self, result: Any) -> str: - clob_value = "" - if result: - if isinstance(result, oracledb.AsyncLOB): - raw_data = await result.read() - if isinstance(raw_data, bytes): - clob_value = raw_data.decode( - "utf-8" - ) # specify the correct encoding - else: - clob_value = raw_data - elif isinstance(result, str): - clob_value = result - else: - raise Exception("Unexpected type:", type(result)) - return clob_value - @_handle_exceptions def similarity_search_by_vector_with_relevance_scores( self, @@ -1329,6 +1304,7 @@ def similarity_search_by_vector_with_relevance_scores( if connection is None: raise ValueError("Failed to acquire a connection.") with connection.cursor() as cursor: + cursor.outputtypehandler = output_type_string_handler cursor.execute(query, embedding=embedding_arr) results = cursor.fetchall() @@ -1337,9 +1313,7 @@ def similarity_search_by_vector_with_relevance_scores( metadata = result[2] or {} doc = Document( - page_content=( - self._get_clob_value(result[1]) if result[1] is not None else "" - ), + page_content=(result[1] if result[1] is not None else ""), metadata=metadata, ) distance = result[3] @@ -1376,6 +1350,7 @@ async def asimilarity_search_by_vector_with_relevance_scores( async def context(connection: Any) -> List: # execute the query with connection.cursor() as cursor: + cursor.outputtypehandler = output_type_string_handler await cursor.execute(query, embedding=embedding_arr) results = await cursor.fetchall() @@ -1384,11 +1359,7 @@ async def context(connection: Any) -> List: metadata = result[2] or {} doc = Document( - page_content=( - await self._aget_clob_value(result[1]) - if result[1] is not None - else "" - ), + page_content=(result[1] if result[1] is not None else ""), metadata=metadata, ) distance = result[3] @@ -1429,11 +1400,12 @@ def similarity_search_by_vector_returning_embeddings( if connection is None: raise ValueError("Failed to acquire a connection.") with connection.cursor() as cursor: + cursor.outputtypehandler = output_type_string_handler cursor.execute(query, embedding=embedding_arr) results = cursor.fetchall() for result in results: - page_content_str = self._get_clob_value(result[1]) + page_content_str = result[1] metadata = result[2] or {} # apply filter if provided and matches; otherwise, add all @@ -1482,11 +1454,12 @@ async def asimilarity_search_by_vector_returning_embeddings( async def context(connection: Any) -> List: # execute the query with connection.cursor() as cursor: + cursor.outputtypehandler = output_type_string_handler await cursor.execute(query, embedding=embedding_arr) results = await cursor.fetchall() for result in results: - page_content_str = await self._aget_clob_value(result[1]) + page_content_str = result[1] metadata = result[2] or {} # apply filter if provided and matches; otherwise, add all From 01f7e425ae749fb5a8d57645e645ad5aacbb506c Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 24 Jul 2025 18:33:56 +0000 Subject: [PATCH 09/12] Use oracledb.DB_TYPE_JSON for JSON binding --- libs/oracledb/langchain_oracledb/embeddings/oracleai.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py index 13d7ec1..6b7c624 100644 --- a/libs/oracledb/langchain_oracledb/embeddings/oracleai.py +++ b/libs/oracledb/langchain_oracledb/embeddings/oracleai.py @@ -122,12 +122,12 @@ def embed_documents(self, texts: List[str]) -> List[List[float]]: vector_array_type = self.conn.gettype("SYS.VECTOR_ARRAY_T") inputs = vector_array_type.newobject(chunks) + cursor.setinputsizes(None, oracledb.DB_TYPE_JSON) cursor.execute( "select t.* " - + "from dbms_vector_chain.utl_to_embeddings(:content, " - + "json(:params)) t", - content=inputs, - params=json.dumps(self.params), + + "from dbms_vector_chain.utl_to_embeddings(:1, " + + "json(:2)) t", + [inputs, self.params], ) for row in cursor: From c9f60c38070ed9568a8c70facebf7e2b0d6db18c Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 24 Jul 2025 18:37:40 +0000 Subject: [PATCH 10/12] Fix license year --- libs/oracledb/scripts/check_imports.py | 2 +- libs/oracledb/tests/unit_tests/__init__.py | 2 +- libs/oracledb/tests/unit_tests/test_imports.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/oracledb/scripts/check_imports.py b/libs/oracledb/scripts/check_imports.py index 2f65ab3..da65db1 100644 --- a/libs/oracledb/scripts/check_imports.py +++ b/libs/oracledb/scripts/check_imports.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ import sys diff --git a/libs/oracledb/tests/unit_tests/__init__.py b/libs/oracledb/tests/unit_tests/__init__.py index 083e045..1de8dae 100644 --- a/libs/oracledb/tests/unit_tests/__init__.py +++ b/libs/oracledb/tests/unit_tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ """All unit tests (lightweight tests).""" diff --git a/libs/oracledb/tests/unit_tests/test_imports.py b/libs/oracledb/tests/unit_tests/test_imports.py index f1ace38..a2f7993 100644 --- a/libs/oracledb/tests/unit_tests/test_imports.py +++ b/libs/oracledb/tests/unit_tests/test_imports.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 Oracle and/or its affiliates. +# Copyright (c) 2025 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ import glob @@ -7,12 +7,12 @@ def test_importable_all() -> None: - for path in glob.glob("../langchain_oci/*"): + for path in glob.glob("../langchain_oracledb/*"): relative_path = Path(path).parts[-1] if relative_path.endswith(".typed"): continue module_name = relative_path.split(".")[0] - module = importlib.import_module("langchain_oci." + module_name) + module = importlib.import_module("langchain_oracledb." + module_name) all_ = getattr(module, "__all__", []) for cls_ in all_: getattr(module, cls_) From e2015b5db2c7cca046c888f13604ffe86336e2d9 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 24 Jul 2025 19:43:34 +0000 Subject: [PATCH 11/12] Update OracleAutonomousDatabaseLoader --- libs/oracledb/README.md | 25 +++------ .../document_loaders/oracleadb_loader.py | 53 +++++++------------ 2 files changed, 27 insertions(+), 51 deletions(-) diff --git a/libs/oracledb/README.md b/libs/oracledb/README.md index a0933cc..3b2a817 100644 --- a/libs/oracledb/README.md +++ b/libs/oracledb/README.md @@ -161,32 +161,21 @@ Load documents from Oracle Autonomous Database using `OracleAutonomousDatabaseLo from langchain_oracledb.document_loaders import OracleAutonomousDatabaseLoader from settings import s -SQL_QUERY = "select prod_id, time_id from sh.costs fetch first 5 rows only" +SQL_QUERY = "select channel_id, channel_desc from sh.channels where channel_desc = :1 fetch first 5 rows only" -doc_loader_1 = OracleAutonomousDatabaseLoader( +doc_loader = OracleAutonomousDatabaseLoader( query=SQL_QUERY, user=s.USERNAME, password=s.PASSWORD, schema=s.SCHEMA, - config_dir=s.CONFIG_DIR, - wallet_location=s.WALLET_LOCATION, - wallet_password=s.PASSWORD, - tns_name=s.TNS_NAME, + dsn=s.DSN, + parameters=["Direct Sales"], ) -doc_1 = doc_loader_1.load() - -doc_loader_2 = OracleAutonomousDatabaseLoader( - query=SQL_QUERY, - user=s.USERNAME, - password=s.PASSWORD, - schema=s.SCHEMA, - connection_string=s.CONNECTION_STRING, - wallet_location=s.WALLET_LOCATION, - wallet_password=s.PASSWORD, -) -doc_2 = doc_loader_2.load() +doc = doc_loader.load() ``` +With mutual TLS authentication (mTLS), wallet_location and wallet_password are required to create the connection, user can create connection by providing either connection string or tns configuration details. With TLS authentication, wallet_location and wallet_password are not required. Bind variable option is provided by argument "parameters". + ### Embeddings #### OracleEmbeddings diff --git a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py index 3d26d38..c19a3ea 100644 --- a/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py +++ b/libs/oracledb/langchain_oracledb/document_loaders/oracleadb_loader.py @@ -16,10 +16,10 @@ class OracleAutonomousDatabaseLoader(BaseLoader): """ - Load from oracle adb + Load from Oracle ADB - Autonomous Database connection can be made by either connection_string - or tns name. wallet_location and wallet_password are required + Autonomous Database connection can be made by connection_string + or tns alias. wallet_location and wallet_password are required for TLS connection. Each document will represent one row of the query result. Columns are written into the `page_content` and 'metadata' in @@ -34,13 +34,12 @@ def __init__( password: str, *, schema: Optional[str] = None, - tns_name: Optional[str] = None, + dsn: Optional[str] = None, config_dir: Optional[str] = None, wallet_location: Optional[str] = None, wallet_password: Optional[str] = None, - connection_string: Optional[str] = None, metadata: Optional[List[str]] = None, - parameters: Optional[Union[list, tuple, dict]] = None, + parameter: Optional[Union[list, tuple, dict]] = None, ): """ init method @@ -48,52 +47,40 @@ def __init__( :param user: username :param password: user password :param schema: schema to run in database - :param tns_name: tns name in tnsname.ora - :param config_dir: directory of config files(tnsname.ora, wallet) - :param wallet_location: location of wallet - :param wallet_password: password of wallet - :param connection_string: connection string to connect to adb instance + :param dsn: data source name used to identify the oracle database to connect to + :param config_dir: directory of config files(tnsname.ora, ewallet.pem) + :param wallet_location: location of ewallet.pem, not required for TLS connections + :param wallet_password: password of wallet, not required for TLS connections :param metadata: metadata used in document - :param parameters: bind variable to use in query + :param parameter: bind variable to use in query """ - # mandatory required arguments. + # Mandatory required arguments. self.query = query self.user = user self.password = password - # schema + # Schema self.schema = schema # TNS connection Method - self.tns_name = tns_name self.config_dir = config_dir - # wallet configuration is required for mTLS connection + # Wallet configuration is required for mTLS connection self.wallet_location = wallet_location self.wallet_password = wallet_password - # connection string connection method - self.connection_string = connection_string - # metadata column self.metadata = metadata - # parameters, e.g bind variable - self.parameters = parameters + # parameter, e.g bind variable + self.parameter = parameter # dsn - self.dsn: Optional[str] - self._set_dsn() - - def _set_dsn(self) -> None: - if self.connection_string: - self.dsn = self.connection_string - elif self.tns_name: - self.dsn = self.tns_name + self.dsn = dsn def _run_query(self) -> List[Dict[str, Any]]: connect_param = {"user": self.user, "password": self.password, "dsn": self.dsn} - if self.dsn == self.tns_name: + if self.config_dir: connect_param["config_dir"] = self.config_dir if self.wallet_location and self.wallet_password: connect_param["wallet_location"] = self.wallet_location @@ -103,9 +90,9 @@ def _run_query(self) -> List[Dict[str, Any]]: connection = oracledb.connect(**connect_param) cursor = connection.cursor() if self.schema: - cursor.execute(f"alter session set current_schema={self.schema}") - if self.parameters: - cursor.execute(self.query, self.parameters) + connection.current_schema = self.schema + if self.parameter: + cursor.execute(self.query, self.parameter) else: cursor.execute(self.query) columns = [col[0] for col in cursor.description] From 99e05e595fbee76a306011bdf4ab2f2c43ca4fe3 Mon Sep 17 00:00:00 2001 From: Elif Sema Balcioglu Date: Thu, 24 Jul 2025 22:41:57 +0000 Subject: [PATCH 12/12] Use quoted identifiers --- .../vectorstores/oraclevs.py | 85 ++- .../vectorstores/test_oraclevs.py | 544 +++++++++--------- 2 files changed, 334 insertions(+), 295 deletions(-) diff --git a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py index 46f885c..919d4e9 100644 --- a/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py +++ b/libs/oracledb/langchain_oracledb/vectorstores/oraclevs.py @@ -14,6 +14,7 @@ import inspect import logging import os +import re import uuid from collections.abc import Awaitable from typing import ( @@ -225,12 +226,20 @@ async def _atable_exists(connection: AsyncConnection, table_name: str) -> bool: raise -def _normalize_oracle_identifier(name: str) -> str: +def _quote_indentifier(name: str) -> str: name = name.strip() - if name.startswith('"') and name.endswith('"'): - return name - else: - return name.upper() + reg = r'^(?:"[^"]+"|[^".]+)(?:\.(?:"[^"]+"|[^".]+))*$' + pattern_validate = re.compile(reg) + + if not pattern_validate.match(name): + raise ValueError(f"Identifier name {name} is not valid.") + + pattern_match = r'"([^"]+)"|([^".]+)' + groups = re.findall(pattern_match, name) + groups = [m[0] or m[1] for m in groups] + groups = [f'"{g}"' for g in groups] + + return ".".join(groups) @_handle_exceptions @@ -245,16 +254,21 @@ def _index_exists( {"AND table_name = :table_name" if table_name else ""} """ + # this is an internal method, index_name and table_name comes with double quotes + index_name = index_name.replace('"', "") + if table_name: + table_name = table_name.replace('"', "") + with connection.cursor() as cursor: # execute the query if table_name: cursor.execute( query, - idx_name=_normalize_oracle_identifier(index_name), - table_name=_normalize_oracle_identifier(table_name), + idx_name=index_name, + table_name=table_name, ) else: - cursor.execute(query, idx_name=_normalize_oracle_identifier(index_name)) + cursor.execute(query, idx_name=index_name) result = cursor.fetchone() # check if the index exists @@ -272,18 +286,21 @@ async def _aindex_exists( {"AND table_name = :table_name" if table_name else ""} """ + # this is an internal method, index_name and table_name comes with double quotes + index_name = index_name.replace('"', "") + if table_name: + table_name = table_name.replace('"', "") + with connection.cursor() as cursor: # execute the query if table_name: await cursor.execute( query, - idx_name=_normalize_oracle_identifier(index_name), - table_name=_normalize_oracle_identifier(table_name), + idx_name=index_name, + table_name=table_name, ) else: - await cursor.execute( - query, idx_name=_normalize_oracle_identifier(index_name) - ) + await cursor.execute(query, idx_name=index_name) result = await cursor.fetchone() # check if the index exists @@ -309,7 +326,7 @@ def _get_distance_function(distance_strategy: DistanceStrategy) -> str: def _get_index_name(base_name: str) -> str: unique_id = str(uuid.uuid4()).replace("-", "") - return f"{base_name}_{unique_id}" + return f'"{base_name}_{unique_id}"' def _get_table_dict(embedding_dim: int) -> Dict: @@ -322,7 +339,6 @@ def _get_table_dict(embedding_dim: int) -> Dict: return cols_dict -@_handle_exceptions def _create_table(connection: Connection, table_name: str, embedding_dim: int) -> None: cols_dict = _get_table_dict(embedding_dim) @@ -365,6 +381,8 @@ def create_index( if connection is None: raise ValueError("Failed to acquire a connection.") if params: + if "idx_name" in params: + params["idx_name"] = _quote_indentifier(params["idx_name"]) if params["idx_type"] == "HNSW": _create_hnsw_index( connection, @@ -425,6 +443,7 @@ def _get_hnsw_index_ddl( raise ValueError(f"Invalid parameter: {key}") else: config = defaults + config["idx_name"] = _get_index_name(str(config["idx_name"])) # base SQL statement idx_name = config["idx_name"] @@ -519,6 +538,7 @@ def _get_ivf_index_ddl( raise ValueError(f"Invalid parameter: {key}") else: config = defaults + config["idx_name"] = _get_index_name(str(config["idx_name"])) # base SQL statement idx_name = config["idx_name"] @@ -577,6 +597,8 @@ async def acreate_index( ) -> None: async def context(connection: Any) -> None: if params: + if "idx_name" in params: + params["idx_name"] = _quote_indentifier(params["idx_name"]) if params["idx_type"] == "HNSW": await _acreate_hnsw_index( connection, @@ -657,6 +679,7 @@ def drop_table_purge(client: Any, table_name: str) -> None: RuntimeError: If an error occurs while dropping the table. """ connection = _get_connection(client) + table_name = _quote_indentifier(table_name) if connection is None: raise ValueError("Failed to acquire a connection.") if _table_exists(connection, table_name): @@ -680,6 +703,7 @@ async def adrop_table_purge(client: Any, table_name: str) -> None: Raises: RuntimeError: If an error occurs while dropping the table. """ + table_name = _quote_indentifier(table_name) async def context(connection: Any) -> None: if await _atable_exists(connection, table_name): @@ -706,6 +730,7 @@ def drop_index_if_exists(client: Any, index_name: str) -> None: RuntimeError: If an error occurs while dropping the index. """ connection = _get_connection(client) + index_name = _quote_indentifier(index_name) if connection is None: raise ValueError("Failed to acquire a connection.") if _index_exists(connection, index_name): @@ -729,6 +754,7 @@ async def adrop_index_if_exists(client: Any, index_name: str) -> None: Raises: RuntimeError: If an error occurs while dropping the index. """ + index_name = _quote_indentifier(index_name) async def context(connection: Any) -> None: if await _aindex_exists(connection, index_name): @@ -874,8 +900,6 @@ async def _handle_context( def output_type_string_handler(cursor: Any, metadata: Any) -> Any: if metadata.type_code is oracledb.DB_TYPE_CLOB: return cursor.var(oracledb.DB_TYPE_LONG, arraysize=cursor.arraysize) - if metadata.type_code is oracledb.DB_TYPE_BLOB: - return cursor.var(oracledb.DB_TYPE_LONG_RAW, arraysize=cursor.arraysize) if metadata.type_code is oracledb.DB_TYPE_NCLOB: return cursor.var(oracledb.DB_TYPE_LONG_NVARCHAR, arraysize=cursor.arraysize) @@ -911,7 +935,7 @@ def __init__( Callable[[str], List[float]], Embeddings, ], - table_name: str, + table_name: str, # case sensitive distance_strategy: DistanceStrategy = DistanceStrategy.EUCLIDEAN_DISTANCE, query: Optional[str] = "What is a Oracle database", params: Optional[Dict[str, Any]] = None, @@ -935,7 +959,7 @@ def __init__( ) embedding_dim = self.get_embedding_dimension() - _create_table(connection, table_name, embedding_dim) + _create_table(connection, self.table_name, embedding_dim) @classmethod @_ahandle_exceptions @@ -969,7 +993,7 @@ async def context(connection: Any) -> None: ) embedding_dim = await self.aget_embedding_dimension() - await _acreate_table(connection, table_name, embedding_dim) + await _acreate_table(connection, self.table_name, embedding_dim) await _handle_context(client, context) @@ -1014,7 +1038,7 @@ def _initialize( ) self.embedding_function = embedding_function self.query = query - self.table_name = table_name + self.table_name = _quote_indentifier(table_name) self.distance_strategy = distance_strategy self.params = params @@ -1311,9 +1335,13 @@ def similarity_search_by_vector_with_relevance_scores( # filter results if filter is provided for result in results: metadata = result[2] or {} + page_content_str = result[1] if result[1] is not None else "" + + if not isinstance(page_content_str, str): + raise Exception("Unexpected type:", type(page_content_str)) doc = Document( - page_content=(result[1] if result[1] is not None else ""), + page_content=page_content_str, metadata=metadata, ) distance = result[3] @@ -1357,9 +1385,12 @@ async def context(connection: Any) -> List: # filter results if filter is provided for result in results: metadata = result[2] or {} + page_content_str = result[1] if result[1] is not None else "" + if not isinstance(page_content_str, str): + raise Exception("Unexpected type:", type(page_content_str)) doc = Document( - page_content=(result[1] if result[1] is not None else ""), + page_content=page_content_str, metadata=metadata, ) distance = result[3] @@ -1405,7 +1436,9 @@ def similarity_search_by_vector_returning_embeddings( results = cursor.fetchall() for result in results: - page_content_str = result[1] + page_content_str = result[1] if result[1] is not None else "" + if not isinstance(page_content_str, str): + raise Exception("Unexpected type:", type(page_content_str)) metadata = result[2] or {} # apply filter if provided and matches; otherwise, add all @@ -1459,7 +1492,9 @@ async def context(connection: Any) -> List: results = await cursor.fetchall() for result in results: - page_content_str = result[1] + page_content_str = result[1] if result[1] is not None else "" + if not isinstance(page_content_str, str): + raise Exception("Unexpected type:", type(page_content_str)) metadata = result[2] or {} # apply filter if provided and matches; otherwise, add all diff --git a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py index 2399d7e..8eb22ca 100644 --- a/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py +++ b/libs/oracledb/tests/integration_tests/vectorstores/test_oraclevs.py @@ -25,6 +25,7 @@ _atable_exists, _create_table, _index_exists, + _quote_indentifier, _table_exists, acreate_index, adrop_index_if_exists, @@ -49,59 +50,52 @@ def test_table_exists_test() -> None: sys.exit(1) # 1. Existing Table:(all capital letters) # expectation:True - _table_exists(connection, "V$TRANSACTION") + _table_exists(connection, _quote_indentifier("V$TRANSACTION")) # 2. Existing Table:(all small letters) # expectation:True - _table_exists(connection, "v$transaction") + _table_exists(connection, _quote_indentifier("v$transaction")) # 3. Non-Existing Table # expectation:false - _table_exists(connection, "Hello") + _table_exists(connection, _quote_indentifier("Hello")) # 4. Invalid Table Name # Expectation:ORA-00903: invalid table name - try: + with pytest.raises(oracledb.Error): _table_exists(connection, "123") - except Exception: - pass # 5. Empty String # Expectation:ORA-00903: invalid table name - try: - _table_exists(connection, "") - except Exception: - pass + with pytest.raises(ValueError): + _table_exists(connection, _quote_indentifier("")) - # 6. Special Character + """# 6. Special Character # Expectation:ORA-00911: #: invalid character after FROM - try: - _table_exists(connection, "##4") - except Exception: - pass + with pytest.raises(oracledb.Error): + _table_exists(connection, "##4")""" # 7. Table name length > 128 # Expectation:ORA-00972: The identifier XXXXXXXXXX...XXXXXXXXXX... # exceeds the maximum length of 128 bytes. - try: - _table_exists(connection, "x" * 129) - except Exception: - pass + with pytest.raises(oracledb.Error): + _table_exists(connection, _quote_indentifier("x" * 129)) # 8. # Expectation:True - _create_table(connection, "TB1", 65535) + _create_table(connection, _quote_indentifier("TB1"), 65535) + assert _table_exists(connection, _quote_indentifier("TB1")) # 9. Toggle Case (like TaBlE) - # Expectation:True - _table_exists(connection, "Tb1") - drop_table_purge(connection, "TB1") + # Expectation:False - case sensitive + assert not _table_exists(connection, _quote_indentifier("Tb1")) + drop_table_purge(connection, _quote_indentifier("TB1")) # 10. Table_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" # Expectation:True - _create_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) - _table_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') - drop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + _create_table(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"'), 545) + assert _table_exists(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) + drop_table_purge(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) @pytest.mark.asyncio @@ -114,15 +108,15 @@ async def test_table_exists_test_async() -> None: sys.exit(1) # 1. Existing Table:(all capital letters) # expectation:True - await _atable_exists(connection, "V$TRANSACTION") + await _atable_exists(connection, _quote_indentifier("V$TRANSACTION")) # 2. Existing Table:(all small letters) # expectation:True - await _atable_exists(connection, "v$transaction") + await _atable_exists(connection, _quote_indentifier("v$transaction")) # 3. Non-Existing Table # expectation:false - await _atable_exists(connection, "Hello") + await _atable_exists(connection, _quote_indentifier("Hello")) # 4. Invalid Table Name # Expectation:ORA-00903: invalid table name @@ -131,34 +125,35 @@ async def test_table_exists_test_async() -> None: # 5. Empty String # Expectation:ORA-00903: invalid table name - with pytest.raises(oracledb.Error): - await _atable_exists(connection, "") + with pytest.raises(ValueError): + await _atable_exists(connection, _quote_indentifier("")) - # 6. Special Character + """# 6. Special Character # Expectation:ORA-00911: #: invalid character after FROM with pytest.raises(oracledb.Error): - await _atable_exists(connection, "##4") + await _atable_exists(connection, "##4")""" # 7. Table name length > 128 # Expectation:ORA-00972: The identifier XXXXXXXXXX...XXXXXXXXXX... # exceeds the maximum length of 128 bytes. with pytest.raises(oracledb.Error): - await _atable_exists(connection, "x" * 129) + await _atable_exists(connection, _quote_indentifier("x" * 129)) # 8. # Expectation:True - await _acreate_table(connection, "TB1", 65535) + await _acreate_table(connection, _quote_indentifier("TB1"), 65535) + assert await _atable_exists(connection, _quote_indentifier("TB1")) # 9. Toggle Case (like TaBlE) - # Expectation:True - await _atable_exists(connection, "Tb1") - await adrop_table_purge(connection, "TB1") + # Expectation:False - case sensitive + assert not await _atable_exists(connection, _quote_indentifier("Tb1")) + await adrop_table_purge(connection, _quote_indentifier("TB1")) # 10. Table_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" # Expectation:True - await _acreate_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) - await _atable_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') - await adrop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + await _acreate_table(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"'), 545) + assert await _atable_exists(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) + await adrop_table_purge(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) ############################ @@ -175,130 +170,114 @@ def test_create_table_test() -> None: # 1. New table - HELLO # Dimension - 100 # Expectation:table is created - _create_table(connection, "HELLO", 100) + _create_table(connection, _quote_indentifier("HELLO"), 100) # 2. Existing table name # HELLO # Dimension - 110 # Expectation:Nothing happens - _create_table(connection, "HELLO", 110) - drop_table_purge(connection, "HELLO") + _create_table(connection, _quote_indentifier("HELLO"), 110) + drop_table_purge(connection, _quote_indentifier("HELLO")) - # 3. New Table - 123 + """# 3. New Table - 123 # Quoted names not valid anymore # Dimension - 100 # Expectation:ORA-00903: invalid table name - try: - _create_table(connection, "123", 100) - drop_table_purge(connection, "123") - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier("123"), 100) + drop_table_purge(connection, _quote_indentifier("123"))""" # 4. New Table - Hello123 # Dimension - 65535 # Expectation:table is created - _create_table(connection, "Hello123", 65535) - drop_table_purge(connection, "Hello123") + _create_table(connection, _quote_indentifier("Hello123"), 65535) + drop_table_purge(connection, _quote_indentifier("Hello123")) # 5. New Table - T1 # Dimension - 65536 # Expectation:ORA-51801: VECTOR column type specification # has an unsupported dimension count ('65536'). - try: - _create_table(connection, "T1", 65536) - drop_table_purge(connection, "T1") - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier("T1"), 65536) + drop_table_purge(connection, _quote_indentifier("T1")) # 6. New Table - T1 # Dimension - 0 # Expectation:ORA-51801: VECTOR column type specification has # an unsupported dimension count (0). - try: - _create_table(connection, "T1", 0) - drop_table_purge(connection, "T1") - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier("T1"), 0) + drop_table_purge(connection, _quote_indentifier("T1")) # 7. New Table - T1 # Dimension - -1 # Expectation:ORA-51801: VECTOR column type specification has # an unsupported dimension count ('-'). - try: - _create_table(connection, "T1", -1) - drop_table_purge(connection, "T1") - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier("T1"), -1) + drop_table_purge(connection, _quote_indentifier("T1")) # 8. New Table - T2 # Dimension - '1000' # Expectation:table is created - _create_table(connection, "T2", int("1000")) - drop_table_purge(connection, "T2") + _create_table(connection, _quote_indentifier("T2"), int("1000")) + drop_table_purge(connection, _quote_indentifier("T2")) # 9. New Table - T3 # Dimension - 100 passed as a variable # Expectation:table is created val = 100 - _create_table(connection, "T3", val) - drop_table_purge(connection, "T3") + _create_table(connection, _quote_indentifier("T3"), val) + drop_table_purge(connection, _quote_indentifier("T3")) - # 10. + '''# 10. # Expectation:ORA-00922: missing or invalid option val2 = """H ello""" - try: - _create_table(connection, val2, 545) - drop_table_purge(connection, val2) - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier(val2), 545) + drop_table_purge(connection, _quote_indentifier(val2))''' # 11. New Table - เคนเคฟเคจเฅเคฆเฅ€ # Dimension - 545 # Expectation:table is created - _create_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) - drop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + _create_table(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"'), 545) + drop_table_purge(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) # 12. # Expectation:failure - user does not exist - try: - _create_table(connection, "U1.TB4", 128) - drop_table_purge(connection, "U1.TB4") - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier("U1.TB4"), 128) + drop_table_purge(connection, _quote_indentifier("U1.TB4")) # 13. # Expectation:table is created - _create_table(connection, '"T5"', 128) - drop_table_purge(connection, '"T5"') + _create_table(connection, _quote_indentifier('"T5"'), 128) + drop_table_purge(connection, _quote_indentifier('"T5"')) - # 14. Toggle Case + """# 14. Toggle Case # Expectation:table creation fails - try: - _create_table(connection, "TaBlE", 128) - drop_table_purge(connection, "TaBlE") - except Exception: - pass + with pytest.raises(oracledb.Error): + _create_table(connection, _quote_indentifier("TaBlE"), 128) + drop_table_purge(connection, _quote_indentifier("TaBlE"))""" # 15. table_name as empty_string # Expectation: ORA-00903: invalid table name - try: - _create_table(connection, "", 128) - drop_table_purge(connection, "") - _create_table(connection, '""', 128) - drop_table_purge(connection, '""') - except Exception: - pass + with pytest.raises(ValueError): + _create_table(connection, _quote_indentifier(""), 128) + drop_table_purge(connection, _quote_indentifier("")) + _create_table(connection, _quote_indentifier('""'), 128) + drop_table_purge(connection, _quote_indentifier('""')) # 16. Arithmetic Operations in dimension parameter # Expectation:table is created n = 1 - _create_table(connection, "T10", n + 500) - drop_table_purge(connection, "T10") + _create_table(connection, _quote_indentifier("T10"), n + 500) + drop_table_purge(connection, _quote_indentifier("T10")) # 17. String Operations in table_name&dimension parameter # Expectation:table is created - _create_table(connection, "YaSh".replace("aS", "ok"), 500) - drop_table_purge(connection, "YaSh".replace("aS", "ok")) + _create_table(connection, _quote_indentifier("YaSh".replace("aS", "ok")), 500) + drop_table_purge(connection, _quote_indentifier("YaSh".replace("aS", "ok"))) @pytest.mark.asyncio @@ -313,114 +292,116 @@ async def test_create_table_test_async() -> None: # 1. New table - HELLO # Dimension - 100 # Expectation:table is created - await _acreate_table(connection, "HELLO", 100) + await _acreate_table(connection, _quote_indentifier("HELLO"), 100) # 2. Existing table name # HELLO # Dimension - 110 # Expectation:Nothing happens - await _acreate_table(connection, "HELLO", 110) - await adrop_table_purge(connection, "HELLO") + await _acreate_table(connection, _quote_indentifier("HELLO"), 110) + await adrop_table_purge(connection, _quote_indentifier("HELLO")) - # 3. New Table - 123 + """# 3. New Table - 123 # Quoted names not valid anymore # Dimension - 100 # Expectation:ORA-00903: invalid table name with pytest.raises(oracledb.Error): - await _acreate_table(connection, "123", 100) - await adrop_table_purge(connection, "123") + await _acreate_table(connection, _quote_indentifier("123"), 100) + await adrop_table_purge(connection, _quote_indentifier("123"))""" # 4. New Table - Hello123 # Dimension - 65535 # Expectation:table is created - await _acreate_table(connection, "Hello123", 65535) - await adrop_table_purge(connection, "Hello123") + await _acreate_table(connection, _quote_indentifier("Hello123"), 65535) + await adrop_table_purge(connection, _quote_indentifier("Hello123")) # 5. New Table - T1 # Dimension - 65536 # Expectation:ORA-51801: VECTOR column type specification # has an unsupported dimension count ('65536'). with pytest.raises(oracledb.Error): - await _acreate_table(connection, "T1", 65536) - await adrop_table_purge(connection, "T1") + await _acreate_table(connection, _quote_indentifier("T1"), 65536) + await adrop_table_purge(connection, _quote_indentifier("T1")) # 6. New Table - T1 # Dimension - 0 # Expectation:ORA-51801: VECTOR column type specification has # an unsupported dimension count (0). with pytest.raises(oracledb.Error): - await _acreate_table(connection, "T1", 0) - await adrop_table_purge(connection, "T1") + await _acreate_table(connection, _quote_indentifier("T1"), 0) + await adrop_table_purge(connection, _quote_indentifier("T1")) # 7. New Table - T1 # Dimension - -1 # Expectation:ORA-51801: VECTOR column type specification has # an unsupported dimension count ('-'). with pytest.raises(oracledb.Error): - await _acreate_table(connection, "T1", -1) - await adrop_table_purge(connection, "T1") + await _acreate_table(connection, _quote_indentifier("T1"), -1) + await adrop_table_purge(connection, _quote_indentifier("T1")) # 8. New Table - T2 # Dimension - '1000' # Expectation:table is created - await _acreate_table(connection, "T2", int("1000")) - await adrop_table_purge(connection, "T2") + await _acreate_table(connection, _quote_indentifier("T2"), int("1000")) + await adrop_table_purge(connection, _quote_indentifier("T2")) # 9. New Table - T3 # Dimension - 100 passed as a variable # Expectation:table is created val = 100 - await _acreate_table(connection, "T3", val) - await adrop_table_purge(connection, "T3") + await _acreate_table(connection, _quote_indentifier("T3"), val) + await adrop_table_purge(connection, _quote_indentifier("T3")) - # 10. + '''# 10. # Expectation:ORA-00922: missing or invalid option val2 = """H ello""" with pytest.raises(oracledb.Error): - await _acreate_table(connection, val2, 545) - await adrop_table_purge(connection, val2) + await _acreate_table(connection, _quote_indentifier(val2), 545) + await adrop_table_purge(connection, _quote_indentifier(val2))''' # 11. New Table - เคนเคฟเคจเฅเคฆเฅ€ # Dimension - 545 # Expectation:table is created - await _acreate_table(connection, '"เคนเคฟเคจเฅเคฆเฅ€"', 545) - await adrop_table_purge(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') + await _acreate_table(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"'), 545) + await adrop_table_purge(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) # 12. # Expectation:failure - user does not exist with pytest.raises(oracledb.Error): - await _acreate_table(connection, "U1.TB4", 128) - await adrop_table_purge(connection, "U1.TB4") + await _acreate_table(connection, _quote_indentifier("U1.TB4"), 128) + await adrop_table_purge(connection, _quote_indentifier("U1.TB4")) # 13. # Expectation:table is created - await _acreate_table(connection, '"T5"', 128) - await adrop_table_purge(connection, '"T5"') + await _acreate_table(connection, _quote_indentifier('"T5"'), 128) + await adrop_table_purge(connection, _quote_indentifier('"T5"')) - # 14. Toggle Case + """# 14. Toggle Case # Expectation:table creation fails with pytest.raises(oracledb.Error): - await _acreate_table(connection, "TaBlE", 128) - await adrop_table_purge(connection, "TaBlE") + await _acreate_table(connection, _quote_indentifier("TaBlE"), 128) + await adrop_table_purge(connection, _quote_indentifier("TaBlE"))""" # 15. table_name as empty_string # Expectation: ORA-00903: invalid table name - with pytest.raises(oracledb.Error): - await _acreate_table(connection, "", 128) - await adrop_table_purge(connection, "") - await _acreate_table(connection, '""', 128) - await adrop_table_purge(connection, '""') + with pytest.raises(ValueError): + await _acreate_table(connection, _quote_indentifier(""), 128) + await adrop_table_purge(connection, _quote_indentifier("")) + await _acreate_table(connection, _quote_indentifier('""'), 128) + await adrop_table_purge(connection, _quote_indentifier('""')) # 16. Arithmetic Operations in dimension parameter # Expectation:table is created n = 1 - await _acreate_table(connection, "T10", n + 500) - await adrop_table_purge(connection, "T10") + await _acreate_table(connection, _quote_indentifier("T10"), n + 500) + await adrop_table_purge(connection, _quote_indentifier("T10")) # 17. String Operations in table_name&dimension parameter # Expectation:table is created - await _acreate_table(connection, "YaSh".replace("aS", "ok"), 500) - await adrop_table_purge(connection, "YaSh".replace("aS", "ok")) + await _acreate_table( + connection, _quote_indentifier("YaSh".replace("aS", "ok")), 500 + ) + await adrop_table_purge(connection, _quote_indentifier("YaSh".replace("aS", "ok"))) ################################## @@ -446,11 +427,9 @@ def test_create_hnsw_index_test() -> None: # 2. Creating same index again # Table_name - TB1 # Expectation:Nothing happens - try: + with pytest.raises(RuntimeError, match="such column list already indexed"): create_index(connection, vs) drop_index_if_exists(connection, "HNSW") - except Exception: - pass drop_table_purge(connection, "TB1") # 3. Create index with following parameters: @@ -466,32 +445,24 @@ def test_create_hnsw_index_test() -> None: # idx_name - "เคนเคฟเคจเฅเคฆเฅ€" # idx_type - HNSW # Expectation:Index created - try: - vs = OracleVS(connection, model1, "TB3", DistanceStrategy.EUCLIDEAN_DISTANCE) - create_index(connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"}) - drop_index_if_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') - except Exception: - pass + vs = OracleVS(connection, model1, "TB3", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"}) + drop_index_if_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') drop_table_purge(connection, "TB3") # 5. idx_name passed empty # Expectation:ORA-01741: illegal zero-length identifier - try: + with pytest.raises(ValueError): vs = OracleVS(connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index(connection, vs, params={"idx_name": '""', "idx_type": "HNSW"}) drop_index_if_exists(connection, '""') - except Exception: - pass drop_table_purge(connection, "TB4") # 6. idx_type left empty # Expectation:Index created - try: - vs = OracleVS(connection, model1, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE) - create_index(connection, vs, params={"idx_name": "Hello", "idx_type": ""}) - drop_index_if_exists(connection, "Hello") - except Exception: - pass + vs = OracleVS(connection, model1, "TB5", DistanceStrategy.EUCLIDEAN_DISTANCE) + create_index(connection, vs, params={"idx_name": "Hello", "idx_type": ""}) + drop_index_if_exists(connection, "Hello") drop_table_purge(connection, "TB5") # 7. efconstruction passed as parameter but not neighbours @@ -548,7 +519,7 @@ def test_create_hnsw_index_test() -> None: drop_index_if_exists(connection, "idx11") drop_table_purge(connection, "TB9") # index not created: - try: + with pytest.raises(RuntimeError): vs = OracleVS(connection, model1, "TB10", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index( connection, @@ -562,10 +533,8 @@ def test_create_hnsw_index_test() -> None: }, ) drop_index_if_exists(connection, "idx11") - except Exception: - pass # index not created: - try: + with pytest.raises(RuntimeError): vs = OracleVS(connection, model1, "TB11", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index( connection, @@ -579,10 +548,8 @@ def test_create_hnsw_index_test() -> None: }, ) drop_index_if_exists(connection, "idx11") - except Exception: - pass # index not created - try: + with pytest.raises(RuntimeError): vs = OracleVS(connection, model1, "TB12", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index( connection, @@ -596,10 +563,8 @@ def test_create_hnsw_index_test() -> None: }, ) drop_index_if_exists(connection, "idx11") - except Exception: - pass # index not created - try: + with pytest.raises(RuntimeError): vs = OracleVS(connection, model1, "TB13", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index( connection, @@ -614,11 +579,9 @@ def test_create_hnsw_index_test() -> None: }, ) drop_index_if_exists(connection, "idx11") - except Exception: - pass # with negative values/out-of-bound values for all 4 of them, we get the same errors # Expectation:Index not created - try: + with pytest.raises(RuntimeError): vs = OracleVS(connection, model1, "TB14", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index( connection, @@ -633,8 +596,6 @@ def test_create_hnsw_index_test() -> None: }, ) drop_index_if_exists(connection, "idx11") - except Exception: - pass drop_table_purge(connection, "TB10") drop_table_purge(connection, "TB11") drop_table_purge(connection, "TB12") @@ -661,7 +622,7 @@ def test_create_hnsw_index_test() -> None: # 11. index_name as # Expectation:U1 not present - try: + with pytest.raises(RuntimeError): vs = OracleVS( connection, model1, "U1.TB16", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -679,17 +640,13 @@ def test_create_hnsw_index_test() -> None: ) drop_index_if_exists(connection, "U1.idx11") drop_table_purge(connection, "TB16") - except Exception: - pass # 12. Index_name size >129 # Expectation:Index not created - try: + with pytest.raises(RuntimeError): vs = OracleVS(connection, model1, "TB17", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index(connection, vs, params={"idx_name": "x" * 129, "idx_type": "HNSW"}) drop_index_if_exists(connection, "x" * 129) - except Exception: - pass drop_table_purge(connection, "TB17") # 13. Index_name size 128 @@ -722,9 +679,10 @@ async def test_create_hnsw_index_test_async() -> None: # 2. Creating same index again # Table_name - TB1 - # Expectation:Nothing happens - await acreate_index(connection, vs) - await adrop_index_if_exists(connection, "HNSW") + # Expectation:Without index name, error happens + with pytest.raises(RuntimeError, match="such column list already indexed"): + await acreate_index(connection, vs) + await adrop_index_if_exists(connection, "HNSW") await adrop_table_purge(connection, "TB1") # 3. Create index with following parameters: @@ -755,7 +713,7 @@ async def test_create_hnsw_index_test_async() -> None: # 5. idx_name passed empty # Expectation:ORA-01741: illegal zero-length identifier - with pytest.raises(RuntimeError): + with pytest.raises(ValueError): vs = await OracleVS.acreate( connection, model1, "TB4", DistanceStrategy.EUCLIDEAN_DISTANCE ) @@ -1010,65 +968,54 @@ def test_index_exists_test() -> None: # Expectation:true vs = OracleVS(connection, model1, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) create_index(connection, vs, params={"idx_name": "idx11", "idx_type": "HNSW"}) - _index_exists(connection, "IDX11") + assert not _index_exists(connection, _quote_indentifier("IDX11")) # 2. Existing Table:(all small letters) # Expectation:true - _index_exists(connection, "idx11") + assert _index_exists(connection, _quote_indentifier("idx11")) # 3. Non-Existing Index # Expectation:False - _index_exists(connection, "Hello") + assert not _index_exists(connection, _quote_indentifier("Hello")) - # 4. Invalid Index Name + """# 4. Invalid Index Name # Qutoted not invalid # Expectation:Error - try: - _index_exists(connection, "123") - except Exception: - pass + with pytest.raises(RuntimeError): + _index_exists(connection, _quote_indentifier("123"))""" # 5. Empty String # Expectation:Error - try: - _index_exists(connection, "") - except Exception: - pass - try: - _index_exists(connection, "") - except Exception: - pass + with pytest.raises(ValueError): + _index_exists(connection, _quote_indentifier("")) - # 6. Special Character + """# 6. Special Character # Expectation:Error - try: - _index_exists(connection, "##4") - except Exception: - pass + with pytest.raises(RuntimeError): + _index_exists(connection, _quote_indentifier("##4"))""" - # 7. Index name length > 128 + """# 7. Index name length > 128 # Expectation:Error - try: - _index_exists(connection, "x" * 129) - except Exception: - pass + with pytest.raises(oracledb.Error): + _index_exists(connection, _quote_indentifier("x" * 129))""" # 8. # Expectation:true - _index_exists(connection, "U1.IDX11") + _index_exists(connection, _quote_indentifier("ONNXUSER.idx11")) # 9. Toggle Case (like iDx11) # Expectation:true - _index_exists(connection, "IdX11") + assert not _index_exists(connection, _quote_indentifier("IdX11")) # 10. Index_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" # Expectation:true - drop_index_if_exists(connection, "idx11") - try: - create_index(connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"}) - _index_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') - except Exception: - pass - drop_table_purge(connection, "TB1") + drop_index_if_exists(connection, _quote_indentifier("idx11")) + create_index( + connection, + vs, + params={"idx_name": _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"'), "idx_type": "HNSW"}, + ) + assert _index_exists(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) + drop_table_purge(connection, _quote_indentifier("TB1")) @pytest.mark.asyncio @@ -1090,49 +1037,56 @@ async def test_index_exists_test_async() -> None: await acreate_index( connection, vs, params={"idx_name": "idx11", "idx_type": "HNSW"} ) - assert await _aindex_exists(connection, "IDX11") + assert not await _aindex_exists(connection, _quote_indentifier("IDX11")) # 2. Existing Table:(all small letters) # Expectation:true - assert await _aindex_exists(connection, "idx11") + assert await _aindex_exists(connection, _quote_indentifier("idx11")) # 3. Non-Existing Index # Expectation:False - assert not await _aindex_exists(connection, "Hello") + assert not await _aindex_exists(connection, _quote_indentifier("Hello")) - # 4. Invalid Index Name + """# 4. Invalid Index Name # Qutoted not invalid # Expectation:Error - assert not await _aindex_exists(connection, "123") + with pytest.raises(RuntimeError): + await _aindex_exists(connection, _quote_indentifier("123"))""" # 5. Empty String # Expectation:Error - assert not await _aindex_exists(connection, "") + with pytest.raises(ValueError): + await _aindex_exists(connection, _quote_indentifier("")) + with pytest.raises(ValueError): + await _aindex_exists(connection, _quote_indentifier("")) - # 6. Special Character + """# 6. Special Character # Expectation:Error - assert not await _aindex_exists(connection, "##4") + with pytest.raises(RuntimeError): + await _aindex_exists(connection, _quote_indentifier("##4"))""" - # 7. Index name length > 128 + """# 7. Index name length > 128 # Expectation:Error - assert not await _aindex_exists(connection, "x" * 129) + with pytest.raises(oracledb.Error): + await _aindex_exists(connection, _quote_indentifier("x" * 129))""" # 8. # Expectation:true - # assert await _aindex_exists(connection, "ONNXUSER.IDX11") + await _aindex_exists(connection, _quote_indentifier("ONNXUSER.idx11")) # 9. Toggle Case (like iDx11) # Expectation:true - assert await _aindex_exists(connection, "IdX11") + assert not await _aindex_exists(connection, _quote_indentifier("IdX11")) # 10. Index_Nameโ†’ "เคนเคฟเคจเฅเคฆเฅ€" # Expectation:true - await adrop_index_if_exists(connection, "idx11") - # with pytest.raises(): # DOESNT? + await adrop_index_if_exists(connection, _quote_indentifier("idx11")) await acreate_index( - connection, vs, params={"idx_name": '"เคนเคฟเคจเฅเคฆเฅ€"', "idx_type": "HNSW"} + connection, + vs, + params={"idx_name": _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"'), "idx_type": "HNSW"}, ) - await _aindex_exists(connection, '"เคนเคฟเคจเฅเคฆเฅ€"') - await adrop_table_purge(connection, "TB1") + assert await _aindex_exists(connection, _quote_indentifier('"เคนเคฟเคจเฅเคฆเฅ€"')) + await adrop_table_purge(connection, _quote_indentifier("TB1")) ################################## @@ -1268,15 +1222,13 @@ def add1(val: str) -> None: # 8. create object with table name of type # Expectation:U1 does not exist - try: + with pytest.raises(RuntimeError): vs_obj = OracleVS(connection, model, "U1.TB14", DistanceStrategy.DOT_PRODUCT) for i in range(1, 10): texts7 = ["Yash{0}".format(i)] ids13 = ["1234{0}".format(i)] vs_obj.add_texts(texts7, ids=ids13) drop_table_purge(connection, "TB14") - except Exception: - pass @pytest.mark.asyncio @@ -2313,6 +2265,7 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: sys.exit(1) drop_table_purge(connection, "TB1") + drop_table_purge(connection, "Tb1") drop_table_purge(connection, "TB2") # LOGGER = logging.getLogger(__name__) @@ -2327,19 +2280,24 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: with caplog.at_level(logging.INFO): vs_obj = OracleVS(connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE) - assert "Table TB1 created successfully..." in caplog.text + assert 'Table "TB1" created successfully...' in caplog.records[-1].message + + with caplog.at_level(logging.INFO): + OracleVS(connection, model, '"TB1"', DistanceStrategy.EUCLIDEAN_DISTANCE) + + assert 'Table "TB1" already exists...' in caplog.records[-1].message with caplog.at_level(logging.INFO): OracleVS(connection, model, "Tb1", DistanceStrategy.EUCLIDEAN_DISTANCE) - assert "Table Tb1 already exists..." in caplog.text + assert 'Table "Tb1" created successfully...' in caplog.records[-1].message with caplog.at_level(logging.INFO): vs_obj2 = OracleVS( connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table TB2 created successfully..." in caplog.text + assert 'Table "TB2" created successfully...' in caplog.records[-1].message vs_obj.add_texts(texts, metadata) @@ -2348,37 +2306,39 @@ def test_index_table_case(caplog: pytest.LogCaptureFixture) -> None: connection, vs_obj, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} ) - assert "Index hnsw_idx2 created successfully..." in caplog.text + assert 'Index "hnsw_idx2" created successfully...' in caplog.records[-1].message - with caplog.at_level(logging.INFO): + with pytest.raises(RuntimeError, match="such column list already indexed"): create_index( connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} ) - assert "Index HNSW_idx2 already exists..." in caplog.text - with pytest.raises( RuntimeError, match="name is already used by an existing object" ): create_index( - connection, vs_obj2, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + connection, vs_obj2, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} ) - drop_index_if_exists(connection, "hnsw_idx2") - - with caplog.at_level(logging.INFO): + with pytest.raises(RuntimeError, match="such column list already indexed"): create_index( - connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} + connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} ) - assert 'Index "hnsw_idx2" created successfully...' in caplog.text + with caplog.at_level(logging.INFO): + drop_index_if_exists(connection, "hnsw_idx2") - with pytest.raises(RuntimeError, match="such column list already indexed"): + assert 'Index "hnsw_idx2" has been dropped.' in caplog.records[-1].message + + with caplog.at_level(logging.INFO): create_index( - connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} ) + assert 'Index "hnsw_idx2" created successfully...' in caplog.records[-1].message + drop_table_purge(connection, "TB1") + drop_table_purge(connection, "Tb1") drop_table_purge(connection, "TB2") @@ -2392,6 +2352,7 @@ async def test_index_table_case_async(caplog: pytest.LogCaptureFixture) -> None: sys.exit(1) await adrop_table_purge(connection, "TB1") + await adrop_table_purge(connection, "Tb1") await adrop_table_purge(connection, "TB2") # LOGGER = logging.getLogger(__name__) @@ -2408,21 +2369,28 @@ async def test_index_table_case_async(caplog: pytest.LogCaptureFixture) -> None: connection, model, "TB1", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table TB1 created successfully..." in caplog.text + assert 'Table "TB1" created successfully...' in caplog.records[-1].message + + with caplog.at_level(logging.INFO): + await OracleVS.acreate( + connection, model, '"TB1"', DistanceStrategy.EUCLIDEAN_DISTANCE + ) + + assert 'Table "TB1" already exists...' in caplog.records[-1].message with caplog.at_level(logging.INFO): await OracleVS.acreate( connection, model, "Tb1", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table Tb1 already exists..." in caplog.text + assert 'Table "Tb1" created successfully...' in caplog.records[-1].message with caplog.at_level(logging.INFO): vs_obj2 = await OracleVS.acreate( connection, model, "TB2", DistanceStrategy.EUCLIDEAN_DISTANCE ) - assert "Table TB2 created successfully..." in caplog.text + assert 'Table "TB2" created successfully...' in caplog.records[-1].message await vs_obj.aadd_texts(texts, metadata) @@ -2431,35 +2399,71 @@ async def test_index_table_case_async(caplog: pytest.LogCaptureFixture) -> None: connection, vs_obj, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} ) - assert "Index hnsw_idx2 created successfully..." in caplog.text + assert 'Index "hnsw_idx2" created successfully...' in caplog.records[-1].message - with caplog.at_level(logging.INFO): + with pytest.raises(RuntimeError, match="such column list already indexed"): await acreate_index( connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} ) - assert "Index HNSW_idx2 already exists..." in caplog.text - with pytest.raises( RuntimeError, match="name is already used by an existing object" ): await acreate_index( - connection, vs_obj2, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + connection, vs_obj2, params={"idx_name": "hnsw_idx2", "idx_type": "HNSW"} ) - await adrop_index_if_exists(connection, "hnsw_idx2") - - with caplog.at_level(logging.INFO): + with pytest.raises(RuntimeError, match="such column list already indexed"): await acreate_index( - connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} + connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} ) - assert 'Index "hnsw_idx2" created successfully...' in caplog.text + with caplog.at_level(logging.INFO): + await adrop_index_if_exists(connection, "hnsw_idx2") - with pytest.raises(RuntimeError, match="such column list already indexed"): + assert 'Index "hnsw_idx2" has been dropped.' in caplog.records[-1].message + + with caplog.at_level(logging.INFO): await acreate_index( - connection, vs_obj, params={"idx_name": "HNSW_idx2", "idx_type": "HNSW"} + connection, vs_obj, params={"idx_name": '"hnsw_idx2"', "idx_type": "HNSW"} ) + assert 'Index "hnsw_idx2" created successfully...' in caplog.records[-1].message + await adrop_table_purge(connection, "TB1") + await adrop_table_purge(connection, "Tb1") await adrop_table_purge(connection, "TB2") + + +def test_quote_identifier() -> None: + # unquoted + assert _quote_indentifier("hello") == '"hello"' + assert _quote_indentifier("--") == '"--"' + assert _quote_indentifier("U1.table") == '"U1"."table"' + assert _quote_indentifier("hnsw_idx2") == '"hnsw_idx2"' + + with pytest.raises(ValueError): + _quote_indentifier('hnsw_"idx2') + + with pytest.raises(ValueError): + _quote_indentifier('"') + + with pytest.raises(ValueError): + _quote_indentifier('"--') + + with pytest.raises(ValueError): + _quote_indentifier(" ") + + # quoted + assert _quote_indentifier('"U1.table"') == '"U1.table"' + assert _quote_indentifier('"U1"."table"') == '"U1"."table"' + assert _quote_indentifier('"he".--') == '"he"."--"' + + with pytest.raises(ValueError): + assert _quote_indentifier('"he"llo"') + + with pytest.raises(ValueError): + assert _quote_indentifier('"he"--') + + # mixed + assert _quote_indentifier('"U1".table') == '"U1"."table"'