diff --git a/README.md b/README.md index f2ebe469..0b739495 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The `langchain-postgres` package implementations of core LangChain abstractions using `Postgres`. -The package is released under the MIT license. +The package is released under the MIT license. Feel free to use the abstraction as provided or else modify them / extend them as appropriate for your own application. @@ -23,22 +23,65 @@ The package currently only supports the [psycogp3](https://www.psycopg.org/psyco pip install -U langchain-postgres ``` -## Change Log +## Usage -0.0.6: -- Remove langgraph as a dependency as it was causing dependency conflicts. -- Base interface for checkpointer changed in langgraph, so existing implementation would've broken regardless. +### Vectorstore -## Usage +> [!NOTE] +> See example for the [PGVector vectorstore here](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/vectorstore.ipynb) +`PGVector` is being deprecated. Please migrate to `PGVectorStore`. +`PGVectorStore` is used for improved performance and manageability. +See the [migration guide](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/migrate_pgvector_to_pgvectorstore.md) for details on how to migrate from `PGVector` to `PGVectorStore`. + +> [!TIP] +> All synchronous functions have corresponding asynchronous functions + +```python +from langchain_postgres import PGEngine, PGVectorStore +from langchain_core.embeddings import DeterministicFakeEmbedding +import uuid + +# Replace these variable values +engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + +VECTOR_SIZE = 768 +embedding = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +engine.init_vectorstore_table( + table_name="destination_table", + vector_size=VECTOR_SIZE, +) + +store = PGVectorStore.create_sync( + engine=engine, + table_name=TABLE_NAME, + embedding_service=embedding, +) + +all_texts = ["Apples and oranges", "Cars and airplanes", "Pineapple", "Train", "Banana"] +metadatas = [{"len": len(t)} for t in all_texts] +ids = [str(uuid.uuid4()) for _ in all_texts] +docs = [ + Document(id=ids[i], page_content=all_texts[i], metadata=metadatas[i]) for i in range(len(all_texts)) +] + +store.add_documents(docs) + +query = "I'd like a fruit." +docs = store.similarity_search(query) +print(docs) +``` + +For a detailed example on `PGVectorStore` see [here](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/pg_vectorstore.ipynb). ### ChatMessageHistory -The chat message history abstraction helps to persist chat message history +The chat message history abstraction helps to persist chat message history in a postgres table. PostgresChatMessageHistory is parameterized using a `table_name` and a `session_id`. -The `table_name` is the name of the table in the database where +The `table_name` is the name of the table in the database where the chat messages will be stored. The `session_id` is a unique identifier for the chat session. It can be assigned @@ -79,7 +122,6 @@ chat_history.add_messages([ print(chat_history.messages) ``` - ### Vectorstore See example for the [PGVector vectorstore here](https://github.com/langchain-ai/langchain-postgres/blob/main/examples/vectorstore.ipynb) diff --git a/examples/migrate_pgvector_to_pgvectorstore.md b/examples/migrate_pgvector_to_pgvectorstore.md new file mode 100644 index 00000000..fab415a4 --- /dev/null +++ b/examples/migrate_pgvector_to_pgvectorstore.md @@ -0,0 +1,174 @@ +# Migrate a `PGVector` vector store to `PGVectorStore` + +This guide shows how to migrate from the [`PGVector`](https://github.com/langchain-ai/langchain-postgres/blob/main/langchain_postgres/vectorstores.py) vector store class to the [`PGVectorStore`](https://github.com/langchain-ai/langchain-postgres/blob/main/langchain_postgres/vectorstore.py) class. + +## Why migrate? + +This guide explains how to migrate your vector data from a PGVector-style database (two tables) to an PGVectoStore-style database (one table per collection) for improved performance and manageability. + +Migrating to the PGVectorStore interface provides the following benefits: + +- **Simplified management**: A single table contains data corresponding to a single collection, making it easier to query, update, and maintain. +- **Improved metadata handling**: It stores metadata in columns instead of JSON, resulting in significant performance improvements. +- **Schema flexibility**: The interface allows users to add tables into any database schema. +- **Improved performance**: The single-table schema can lead to faster query execution, especially for large collections. +- **Clear separation**: Clearly separate table and extension creation, allowing for distinct permissions and streamlined workflows. +- **Secure Connections:** The PGVectorStore interface creates a secure connection pool that can be easily shared across your application using the `engine` object. + +## Migration process + +> **_NOTE:_** The langchain-core library is installed to use the Fake embeddings service. To use a different embedding service, you'll need to install the appropriate library for your chosen provider. Choose embeddings services from [LangChain's Embedding models](https://python.langchain.com/v0.2/docs/integrations/text_embedding/). + +While you can use the existing PGVector database, we **strongly recommend** migrating your data to the PGVectorStore-style schema to take full advantage of the performance benefits. + +### (Recommended) Data migration + +1. **Create a PG engine.** + + ```python + from langchain_postgres import PGEngine + + # Replace these variable values + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + ``` + + > **_NOTE:_** All sync methods have corresponding async methods. + +2. **Create a new table to migrate existing data.** + + ```python + # Vertex AI embeddings uses a vector size of 768. + # Adjust this according to your embeddings service. + VECTOR_SIZE = 768 + + engine.init_vectorstore_table( + table_name="destination_table", + vector_size=VECTOR_SIZE, + ) + ``` + + **(Optional) Customize your table.** + + When creating your vectorstore table, you have the flexibility to define custom metadata and ID columns. This is particularly useful for: + + - **Filtering**: Metadata columns allow you to easily filter your data within the vectorstore. For example, you might store the document source, date, or author as metadata for efficient retrieval. + - **Non-UUID Identifiers**: By default, the id_column uses UUIDs. If you need to use a different type of ID (e.g., an integer or string), you can define a custom id_column. + + ```python + metadata_columns = [ + Column(f"col_0_{collection_name}", "VARCHAR"), + Column(f"col_1_{collection_name}", "VARCHAR"), + ] + engine.init_vectorstore_table( + table_name="destination_table", + vector_size=VECTOR_SIZE, + metadata_columns=metadata_columns, + id_column=Column("langchain_id", "VARCHAR"), + ) + ``` + +3. **Create a vector store object to interact with the new data.** + + > **_NOTE:_** The `FakeEmbeddings` embedding service is only used to initialise a vector store object, not to generate any embeddings. The embeddings are copied directly from the PGVector table. + + ```python + from langchain_postgres import PGVectorStore + from langchain_core.embeddings import FakeEmbeddings + + destination_vector_store = PGVectorStore.create_sync( + engine, + embedding_service=FakeEmbeddings(size=VECTOR_SIZE), + table_name="destination_table", + ) + ``` + + If you have any customisations on the metadata or the id columns, add them to the vector store as follows: + + ```python + from langchain_postgres import PGVectorStore + from langchain_core.embeddings import FakeEmbeddings + + destination_vector_store = PGVectorStore.create_sync( + engine, + embedding_service=FakeEmbeddings(size=VECTOR_SIZE), + table_name="destination_table", + metadata_columns=[col.name for col in metadata_columns], + id_column="langchain_id", + ) + ``` + +4. **Migrate the data to the new table.** + + ```python + from langchain_postgres.utils.pgvector_migrator import amigrate_pgvector_collection + + migrate_pgvector_collection( + engine, + # Set collection name here + collection_name="collection_name", + vector_store=destination_vector_store, + # This deletes data from the original table upon migration. You can choose to turn it off. + delete_pg_collection=True, + ) + ``` + + The data will only be deleted from the original table once all of it has been successfully copied to the destination table. + +> **TIP:** If you would like to migrate multiple collections, you can use the `alist_pgvector_collection_names` method to get the names of all collections, allowing you to iterate through them. +> +> ```python +> from langchain_postgres.utils.pgvector_migrator import alist_pgvector_collection_names +> +> all_collection_names = list_pgvector_collection_names(engine) +> print(all_collection_names) +> ``` + +### (Not Recommended) Use PGVectorStore interface on PGVector databases + +If you choose not to migrate your data, you can still use the PGVectorStore interface with your existing PGVector database. However, you won't benefit from the performance improvements of the PGVectorStore-style schema. + +1. **Create an PGVectorStore engine.** + + ```python + from langchain_postgres import PGEngine + + # Replace these variable values + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + ``` + + > **_NOTE:_** All sync methods have corresponding async methods. + +2. **Create a vector store object to interact with the data.** + + Use the embeddings service used by your database. See [langchain docs](https://python.langchain.com/docs/integrations/text_embedding/) for reference. + + ```python + from langchain_postgres import PGVectorStore + from langchain_core.embeddings import FakeEmbeddings + + vector_store = PGVectorStore.create_sync( + engine=engine, + table_name="langchain_pg_embedding", + embedding_service=FakeEmbeddings(size=VECTOR_SIZE), + content_column="document", + metadata_json_column="cmetadata", + metadata_columns=["collection_id"], + id_column="id", + ) + ``` + +3. **Perform similarity search.** + + Filter by collection id: + + ```python + vector_store.similarity_search("query", k=5, filter=f"collection_id='{uuid}'") + ``` + + Filter by collection id and metadata: + + ```python + vector_store.similarity_search( + "query", k=5, filter=f"collection_id='{uuid}' and cmetadata->>'col_name' = 'value'" + ) + ``` \ No newline at end of file diff --git a/examples/pg_vectorstore.ipynb b/examples/pg_vectorstore.ipynb new file mode 100644 index 00000000..e013086d --- /dev/null +++ b/examples/pg_vectorstore.ipynb @@ -0,0 +1,674 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# PGVectorStore & PGEngine\n", + "\n", + "This is an implementation of a LangChain vectorstore using `postgres` as the backend.\n", + "\n", + "This notebook goes over how to use `PGVectorStore` to store vector embeddings." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "IR54BmgvdHT_" + }, + "source": [ + "### 🦜🔗 Library Installation\n", + "Install the integration library, `langchain-postgres`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "0ZITIDE160OD", + "outputId": "e184bc0d-6541-4e0a-82d2-1e216db00a2d" + }, + "outputs": [], + "source": [ + "%pip install --upgrade --quiet langchain-postgres" + ] + }, + { + "cell_type": "markdown", + "id": "f8f2830ee9ca1e01", + "metadata": { + "id": "f8f2830ee9ca1e01" + }, + "source": [ + "## Basic Usage" + ] + }, + { + "cell_type": "markdown", + "id": "OMvzMWRrR6n7", + "metadata": { + "id": "OMvzMWRrR6n7" + }, + "source": [ + "### Set the postgres connection url" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "irl7eMFnSPZr", + "metadata": { + "id": "irl7eMFnSPZr" + }, + "outputs": [], + "source": [ + "# @title Set Your Values Here { display-mode: \"form\" }\n", + "POSTGRES_USER = \"postgres-user\" # @param {type: \"string\"}\n", + "POSTGRES_PASSWORD = \"postgres-password\" # @param {type: \"string\"}\n", + "POSTGRES_HOST = \"postgres-host\" # @param {type: \"string\"}\n", + "POSTGRES_PORT = \"postgres-port\" # @param {type: \"string\"}\n", + "POSTGRES_DB = \"postgres-db-name\" # @param {type: \"string\"}\n", + "TABLE_NAME = \"vectorstore\" # @param {type: \"string\"}\n", + "VECTOR_SIZE = 768 # @param {type: \"int\"}\n", + "\n", + "CONNECTION_STRING = (\n", + " f\"postgresql+asyncpg://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}\"\n", + " f\":{POSTGRES_PORT}/{POSTGRES_DB}\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*Note:* `PGVectorStore` can only be used with the `postgresql+asyncpg` driver." + ] + }, + { + "cell_type": "markdown", + "id": "QuQigs4UoFQ2", + "metadata": { + "id": "QuQigs4UoFQ2" + }, + "source": [ + "### PGEngine Connection Pool\n", + "\n", + "One of the requirements and arguments to establish PostgreSQL as a vector store is a `PGEngine` object. The `PGEngine` configures a shared connection pool to your Postgres database. This is an industry best practice to manage number of connections and to reduce latency through cached database connections.\n", + "\n", + "To create a `PGEngine` using `PGEngine.from_connection_string()` you need to provide:\n", + "\n", + "1. `url` : Connection string using the `postgresql+asyncpg` driver.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Note:** This tutorial demonstrates the async interface. All async methods have corresponding sync methods." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_postgres import PGEngine\n", + "\n", + "engine = PGEngine.from_connection_string(url=CONNECTION_STRING)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To create a `PGEngine` using `PGEngine.from_engine()` you need to provide:\n", + "\n", + "1. `engine` : An object of `AsyncEngine`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sqlalchemy.ext.asyncio import create_async_engine\n", + "\n", + "# Create an SQLAlchemy Async Engine\n", + "engine = create_async_engine(\n", + " CONNECTION_STRING,\n", + ")\n", + "\n", + "pg_engine = PGEngine.from_engine(engine=engine)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "D9Xs2qhm6X56" + }, + "source": [ + "### Initialize a table\n", + "The `PGVectorStore` class requires a database table. The `PGEngine` engine has a helper method `ainit_vectorstore_table()` that can be used to create a table with the proper schema for you." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "avlyHEMn6gzU" + }, + "outputs": [], + "source": [ + "await engine.ainit_vectorstore_table(\n", + " table_name=TABLE_NAME,\n", + " vector_size=VECTOR_SIZE,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Optional Tip: 💡\n", + "You can also specify a schema name by passing `schema_name` wherever you pass `table_name`. Eg:\n", + "\n", + "```python\n", + "SCHEMA_NAME=\"my_schema\"\n", + "\n", + "await engine.ainit_vectorstore_table(\n", + " table_name=TABLE_NAME,\n", + " vector_size=768,\n", + " schema_name=SCHEMA_NAME, # Default: \"public\"\n", + ")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create an embedding class instance\n", + "\n", + "You can use any [LangChain embeddings model](https://python.langchain.com/docs/integrations/text_embedding/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Vb2RJocV9_LQ", + "outputId": "37f5dc74-2512-47b2-c135-f34c10afdcf4" + }, + "outputs": [], + "source": [ + "from langchain_cohere import CohereEmbeddings\n", + "\n", + "embedding = CohereEmbeddings()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e1tl0aNx7SWy" + }, + "source": [ + "### Initialize a default PGVectorStore" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "z-AZyzAQ7bsf" + }, + "outputs": [], + "source": [ + "from langchain_postgres import PGVectorStore\n", + "\n", + "store = await PGVectorStore.create(\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " # schema_name=SCHEMA_NAME,\n", + " embedding_service=embedding,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Initialize Vector Store with documents\n", + "\n", + "This is a great way to get started quickly. However, the default method is recommended for most applications to avoid accidentally adding duplicate documents." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.documents import Document\n", + "import uuid\n", + "\n", + "docs = [\n", + " Document(\n", + " page_content=\"Red Apple\",\n", + " metadata={\"description\": \"red\", \"content\": \"1\", \"category\": \"fruit\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Banana Cavendish\",\n", + " metadata={\"description\": \"yellow\", \"content\": \"2\", \"category\": \"fruit\"},\n", + " ),\n", + " Document(\n", + " page_content=\"Orange Navel\",\n", + " metadata={\"description\": \"orange\", \"content\": \"3\", \"category\": \"fruit\"},\n", + " ),\n", + "]\n", + "ids = [str(uuid.uuid4()) for i in range(len(docs))]\n", + "\n", + "store_with_documents = await PGVectorStore.afrom_documents(\n", + " documents=docs,\n", + " ids=ids,\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " # schema_name=SCHEMA_NAME,\n", + " embedding_service=embedding,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add texts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "all_texts = [\"Apples and oranges\", \"Cars and airplanes\", \"Pineapple\", \"Train\", \"Banana\"]\n", + "metadatas = [{\"len\": len(t)} for t in all_texts]\n", + "ids = [str(uuid.uuid4()) for _ in all_texts]\n", + "\n", + "await store.aadd_texts(all_texts, metadatas=metadatas, ids=ids)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete texts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await store.adelete([ids[1]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query = \"I'd like a fruit.\"\n", + "docs = await store.asimilarity_search(query)\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents by vector" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "query_vector = embedding.embed_query(query)\n", + "docs = await store.asimilarity_search_by_vector(query_vector, k=2)\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Add a Index\n", + "Speed up vector search queries by applying a vector index. Learn more about [vector indexes](https://cloud.google.com/blog/products/databases/faster-similarity-search-performance-with-pgvector-indexes)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_postgres.indexes import IVFFlatIndex\n", + "\n", + "index = IVFFlatIndex()\n", + "await store.aapply_vector_index(index)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Re-index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await store.areindex() # Re-index using default index name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Remove an index" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await store.adrop_vector_index() # Delete index using default name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a custom Vector Store\n", + "A Vector Store can take advantage of relational data to filter similarity searches.\n", + "\n", + "Create a new table with custom metadata columns.\n", + "You can also re-use an existing table which already has custom columns for a Document's id, content, embedding, and/or metadata." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_postgres import Column\n", + "\n", + "# Set table name\n", + "TABLE_NAME = \"vectorstore_custom\"\n", + "# SCHEMA_NAME = \"my_schema\"\n", + "\n", + "await engine.ainit_vectorstore_table(\n", + " table_name=TABLE_NAME,\n", + " # schema_name=SCHEMA_NAME,\n", + " vector_size=VECTOR_SIZE,\n", + " metadata_columns=[Column(\"len\", \"INTEGER\")],\n", + ")\n", + "\n", + "\n", + "# Initialize PGVectorStore\n", + "custom_store = await PGVectorStore.create(\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " # schema_name=SCHEMA_NAME,\n", + " embedding_service=embedding,\n", + " metadata_columns=[\"len\"],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents with metadata filter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "# Add texts to the Vector Store\n", + "all_texts = [\"Apples and oranges\", \"Cars and airplanes\", \"Pineapple\", \"Train\", \"Banana\"]\n", + "metadatas = [{\"len\": len(t)} for t in all_texts]\n", + "ids = [str(uuid.uuid4()) for _ in all_texts]\n", + "await custom_store.aadd_texts(all_texts, metadatas=metadatas, ids=ids)\n", + "\n", + "# Use string filter on search\n", + "docs = await custom_store.asimilarity_search(query, filter=\"len >= 6\")\n", + "\n", + "print(docs)\n", + "\n", + "# Use a dictionary filter on search\n", + "docs = await custom_store.asimilarity_search(query, filter={\"len\": {\"$gte\": 6}})\n", + "\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Vector Store using existing table\n", + "\n", + "A Vector Store can be built up on an existing table.\n", + "\n", + "Assuming there's a pre-existing table in PG DB: `products`, which stores product details for an eComm venture.\n", + "\n", + "
\n", + " Click for Table Schema Details\n", + "\n", + " ### SQL query for table creation\n", + " ```\n", + " CREATE TABLE products (\n", + " product_id SERIAL PRIMARY KEY,\n", + " name VARCHAR(255) NOT NULL,\n", + " description TEXT,\n", + " price_usd DECIMAL(10, 2) NOT NULL,\n", + " category VARCHAR(255),\n", + " quantity INT DEFAULT 0,\n", + " sku VARCHAR(255) UNIQUE NOT NULL,\n", + " image_url VARCHAR(255),\n", + " metadata JSON,\n", + " embed vector(768) DEFAULT NULL --> vector dimensions depends on the embedding model\n", + " );\n", + " ```\n", + " ### Insertion of records\n", + " ```\n", + "INSERT INTO\n", + " products (name,\n", + " description,\n", + " price_usd,\n", + " category,\n", + " quantity,\n", + " sku,\n", + " image_url,\n", + " METADATA,\n", + " embed)\n", + "VALUES\n", + " ('Laptop', 'High-performance gaming laptop', 1200.00, 'Electronics', 10, 'SKU12345', 'https://example.com/laptop.jpg', '{\"category\" : \"Electronics\", \"name\" : \"Laptop\", \"description\" : \"High-performance gaming laptop\"}', ARRAY[0.028855365,-0.012488421,0.006031946,0.0041402685,0.058347773,0.034766156,0.0033533745,0.02021188,0.022670388,0.049201276,0.029006215,-0.00986186,-0.052214462,-0.012280585,0.023684537,-0.059519604,0.001378169,-0.04670758,0.020753963,0.0013795564,0.013659675,0.013842887,-0.011299884,-0.03746782,-0.024693582,-0.07013125,0.030126512,-0.028513059,-0.045777187,0.020505989,-0.05952914,0.0015648323,-0.050879195,0.006477519,-0.007886009,-0.02629686,-0.0161126,0.0314275,-0.0328995,0.0265609,-0.01530363,-0.019561788,-0.04535006,0.030131247,0.05462397,-0.0122205755,0.009777537,-0.0049046725,0.02023674,-0.064513534,0.041379478,0.006994005,0.045187026,-0.029661352,0.019398877,-0.02221874,-0.017291287,-0.016321573,-0.033429787,-0.009547383,0.031690586,0.009064364,-0.015285908,0.076494075,0.010917006,-0.016593782,-0.018348552,0.017040739,0.05943369,-0.020822933,0.009285482,0.027736548,0.07029796,-0.0644397,-0.037717465,-0.047550958,-0.0054535423,0.047678974,0.060069297,-0.015072207,-0.04320405,-0.0019738402,-0.061910342,-0.034316592,-0.023359261,0.057676528,0.0054635284,0.042063717,0.020484874,0.005591504,-0.008757174,-0.0153757995,0.04932489,-0.04626516,0.0004756786,0.03749645,0.018522505,-0.015642159,-0.00842546,-0.06284679,-0.006150201,-0.061204597,0.0008340049,0.0040505463,0.014210282,-0.009027461,-0.014203488,0.030791085,-0.022282222,0.0011378798,-0.047313087,-0.008226634,-0.03726029,-0.04307269,0.04519085,-0.021895533,0.019570287,0.08584432,-0.003815025,0.021276724,0.027253378,-0.01660856,0.056772888,0.053538952,0.02739156,0.04655151,0.021516826,0.064367436,-0.021094408,0.0149244,-0.009901731,-0.04166729,-0.0032499651,0.022982895,0.063407354,0.04826923,0.056767307,-0.024418632,0.063300684,0.08071309,-0.054988176,0.01652395,-0.014671885,0.000837919,-0.044569198,0.03651631,-0.016364796,0.0053244857,0.051150765,-0.01878448,0.005112729,-0.0011729974,-0.052268386,0.034706745,0.05072015,0.0052968785,0.021704907,0.045661792,0.002976117,-0.02205154,0.037168674,0.002627892,0.018275578,0.032312263,-0.06719407,-0.056915596,-0.019727554,0.0009450171,0.0029568567,0.047435578,0.033826437,-0.009351167,-0.05718618,-0.062166944,-0.005684254,-0.009788955,0.016364967,0.0122847315,-0.016126394,0.012999976,-0.075272575,0.017478324,0.03005914,0.024401167,0.0099941185,-0.043311242,0.032115143,0.0047207233,-0.034337096,0.0054743756,-0.0024234303,0.012045114,0.032277416,-0.019994166,0.012312445,0.021211047,-0.037350595,0.0017910452,0.04450775,0.0054527316,0.03591427,0.029365221,0.0009824947,-0.006488191,0.034008037,0.01649739,0.07955305,-0.035204325,0.0056851353,-0.0086927805,-0.032573096,0.0010878195,-0.061459325,0.027879931,0.015068312,0.032717325,0.03890655,0.01902891,0.016527452,-0.0020142202,0.025338948,-0.0016015576,-0.06429177,-0.0041105347,-0.025726322,0.09078289,-0.03174613,0.015951345,0.009411334,-0.03598392,0.034463316,0.010011217,-0.009883364,-0.008042991,0.040896636,-0.025115138,0.048056312,0.028382989,0.007793395,0.019581616,-0.02584373,0.04317992,0.025689745,0.02035658,-0.05990108,-0.0007803719,-0.06793038,-0.02130707,0.0048890263,0.042799927,-0.009928141,-0.003192067,0.008781545,0.024785394,-0.07565836,-0.043356933,-0.067785084,-0.019649943,-0.024896448,-0.008327102,-0.015189734,-0.0140810255,0.0049958434,-0.015353841,0.020730853,0.028829988,-0.022614283,-0.03751693,0.011577282,0.031927988,-0.024855413,-0.042680055,0.08018929,-0.0021632465,-0.017928878,-0.0030442774,-0.005651566,-0.0010570051,-0.040446285,-0.00189408,0.06388222,0.0024985478,0.004886204,-0.05113467,-0.019480383,0.049765434,0.0077566532,-0.07356923,0.011988718,-0.020965552,-0.04025921,-0.032686763,-0.0053743063,-0.015599607,-0.03576176,0.00907552,-0.044702522,0.038329247,0.046024352,0.02194124,0.01844749,0.004619246,-0.029577129,-0.031205669,0.00896738,0.0115034515,0.013058729,0.01372364,0.03063813,-0.0316296,-0.04826321,-0.049244087,-0.037644744,0.019473651,0.059536345,0.04033204,-0.06602803,0.050612085,-0.027031716,0.04213856,-0.015262794,0.07257449,0.044631373,-0.0151061565,0.012033797,0.0009732858,-0.014827035,0.046652585,-0.042083394,-0.0436095,-0.035586536,0.026696088,-0.004066648,0.06954644,-0.029623765,-0.020358749,-0.04957031,0.01740737,-0.017026579,0.011162373,0.0487351,-0.031720005,-0.050231773,-0.089686565,-0.014156863,-0.02636994,0.015916161,-0.025308851,0.02081637,-0.02257452,0.021604244,0.10139386,-0.03208752,-0.008580313,-0.008898747,-0.06853021,0.04102758,0.041922912,0.047566738,-0.0341902,-0.07725792,0.005653997,0.00021225312,-0.0104829185,0.001749244,0.011929626,0.078264005,0.036519475,-0.0073295147,0.021337496,-0.008336836,-0.035804152,0.010720447,0.007127837,-0.053885818,-0.009795316,-0.05424524,-0.003111704,0.019710006,-0.012413589,0.02320744,0.024137065,0.023079542,0.0030920266,0.013961592,0.0040291087,0.020265838,0.041183334,-0.0029272675,-0.018539282,-0.011489972,0.017938145,0.025854694,0.033188265,-0.042004097,-0.0106819095,-0.045249976,-0.06986475,0.030204961,-0.032193515,-0.00095170306,-0.0107111735,-0.017970158,-0.02740307,-0.06307846,-0.031544626,0.004178074,0.016592229,-0.032037992,-0.030618787,0.008946463,0.03110429,0.0207187,-0.016861247,-0.08070464,-0.03067543,0.067448415,-0.041909873,-0.0048193526,0.018761802,0.020243261,0.024184326,0.002299031,-0.014152546,-0.035749547,-0.0071563246,0.050069712,-0.027215304,0.049641047,0.02778935,0.070745096,0.023794815,0.0029510225,0.0069351746,-0.034430653,-0.085317925,-0.036851004,0.023848707,0.035138704,-0.017030267,0.041982725,0.014077844,0.012787886,-0.029716792,-0.024732213,-0.059604853,0.024058796,-0.027469097,0.02969232,-0.06889772,-0.034953564,-0.0678685,0.02039748,-0.073483475,-0.04067064,-0.023628144,0.052601792,0.10005532,0.0027910264,-0.00044562414,0.025615653,0.008896907,-0.016369712,-0.030180404,0.026393086,-0.02041892,0.0072918,-0.018448602,0.020845268,0.006290655,-0.010850651,-0.035378493,-0.01083432,0.012116494,-0.045438327,0.05191333,-0.082797736,0.042320468,0.039703712,0.00923727,0.03598509,-0.064069025,0.049349498,0.007205401,-0.0079013845,0.015407162,-0.049755134,-0.0335355,-0.033252683,0.025886077,-0.043650113,-0.021745201,-0.046847582,-0.02873071,-0.01435186,0.01642749,-0.030346846,0.00564007,0.0074587157,0.027222605,-0.024691164,0.007528186,-0.04551536,-0.011026097,0.091698915,-0.062147886,0.0013525741,-0.0065618614,-0.030818032,0.024246406,-0.010786434,0.006758053,-0.016815495,0.071824,0.022536254,-0.026362726,-0.066206455,0.011966612,0.06430261,0.021586932,0.032340884,-0.015460002,-0.0963993,-0.0041012894,0.026189657,-0.101343565,0.038662393,0.07043264,-0.0373032,0.0038455573,-0.017408002,0.12948644,-0.056175977,0.02693295,-0.033682294,-0.032874268,0.0016187532,0.023056049,0.06884863,0.04350595,0.02135146,-0.059129357,-0.0055416543,0.0098204445,-0.008596177,-0.04332969,-0.012624592,-0.09298762,0.041691724,-0.014171953,0.004045705,0.009756654,0.059401184,-0.02852561,0.006892971,-0.019445946,-0.013781522,-0.03458903,-0.001079532,-0.008455719,-0.025446072,-0.03641567,-0.034449898,0.004487285,0.07899037,0.031314176,-0.031828023,0.031026838,0.034468375,0.0166286,0.032397788,0.02265452,0.07575427,0.015329588,0.05969185,-0.049144097,-0.043501142,0.031721197,-0.03434621,0.04558533,-0.00039121095,0.00093291467,0.033810064,0.0131731015,-0.0161992,0.039637238,0.0018543458,-0.041811496,-0.01406263,-0.020126836,-0.011859638,0.029031854,0.018889664,0.015262868,-0.03756649,-0.024570176,0.02538295,0.0038968727,-0.06393701,0.00093783275,-0.05943941,-0.062095385,0.08169533,-0.026443593,0.045758378,-0.026765708,-0.023990292,-0.028646782,0.0013627055,0.0022589415,0.009424216,-0.004252787,0.01159273,-0.0393901,-0.02593045,-0.04785985,0.023880653,0.012857186,-0.028907716,-0.05117687,-0.017512657,-0.035777926,0.01183514,0.025101895,0.089760125,-0.009716518,0.012040118,-0.023447596,0.057904292,0.03486462,-0.014875794,0.05191007,0.002385196,0.016686346,-0.052348964,-0.029286617,0.023832947,-0.02915365,0.007727999,-0.012708917,-0.055755604,-0.0073897606,0.032306697,0.02891973,-0.029123511,0.08987496,0.049180396,-0.08122004,-0.029804248,0.03262262,-0.06680825,0.016717656,0.0038353673,0.021287518,0.0018424556,-0.0041867862,-0.0011719886,-0.044280436,0.02019424,-0.052992586,-0.05063449,0.039644204,-0.0494374,-0.033791043,-0.0041454337,-0.032513123,-0.073564336,-0.04585872,0.0023792102,0.027335508,-0.06999816,0.04888005,0.026423248,0.021874929,0.010904174,0.060097646,-0.034017522,0.05548881,-0.024519302,0.049890403,-0.015645353,-0.060680103,0.017045638,0.019808227,0.025153033,0.0040058065,0.053807795,0.034485374,-0.053428553,-0.0034872151,0.033813756,-0.03047597,0.007858348,0.024711734,0.060215656,0.008143574,-0.0070263194,0.0048007956,0.015641727,0.052094024,-0.049206913,0.016296484,-0.0059813466,0.040864628,0.013278136,-0.012139221,-0.04106141,0.0144868875,0.0013842004,0.021345256,0.04826021,-0.06929805,-0.021199407,0.00090551435,0.009481861,-0.0017141728,0.028452767,-0.019797614,0.038415838,0.056153923,-0.014074272,-0.00823969,-0.00050664565,-0.07698735,-0.025168924,0.057516575,-0.07501726,0.037316702,-0.02765656,-0.011325112,0.058868058,-0.010426108,-0.013318932,-0.0016809561,-0.062076304,0.027063645,-0.020674324,0.06843111,0.018448142,-0.04226709,-0.015164476,-0.008888517,0.040828817,0.048462827,0.00942803,-0.019631634,0.020950766,-0.0003345382,-0.030098192,0.022870619,-0.0017267349,-0.009055838,-0.012781693,0.07583533,0.045031916,-0.02076535,-0.07310905,-0.011597339,-0.00062336307,0.005723161,-0.018269768,0.020560576,0.023111053,-0.00881239,0.0052197427,0.022200806,0.013797317,0.019722437]::vector(768)),\n", + " ('Smartphone', 'Latest model with high-resolution camera', 800.00, 'Electronics', 15, 'SKU12346', 'https://example.com/smartphone.jpg', '{\"category\" : \"Electronics\", \"name\" : \"Smartphone\", \"description\" : \"Latest model with high-resolution camera\"}', ARRAY[0.031757303,-0.030950155,-0.058881454,-0.05073203,0.053704526,-0.01064694,0.030361004,0.0036670829,-0.014013894,0.022840602,0.06545107,0.0108244,0.009321064,-0.0236112,0.0098358095,-0.038861487,-0.011348891,-0.011887714,0.011245335,-0.018139482,0.03049321,-0.030338986,-0.001923893,0.011787388,-0.01825618,-0.050398953,0.0036137043,-0.04487695,-0.021582587,0.023590472,-0.051335085,0.08021365,-0.06793676,-0.00514603,0.024418706,-0.054447155,-0.050472837,0.010439093,-0.017847419,0.07124281,0.004419413,-0.028902968,-0.062286377,-0.02737251,0.048311986,-0.029160773,0.0059961462,0.0344943,0.037635062,-0.081315145,0.025175434,-0.0050063017,0.023545247,-0.015210805,-0.035123624,-0.020403884,0.014771475,0.015879042,0.0029214756,0.011768866,0.004276383,-0.009031657,-0.050000243,0.059927624,-0.03906005,-0.027238877,-0.04796615,0.03084268,0.07360646,-0.028875567,0.027232852,0.015592421,0.07156161,-0.059652634,-0.04831314,-0.049740285,-0.017305655,0.10253246,0.016519215,-0.0021727297,-0.0063062175,-0.0015423468,-0.03617129,-0.03982753,-0.059866134,0.082323685,-0.01662162,-0.0048025097,0.011876321,0.08410362,-0.006159452,-0.0008565244,0.04274695,-0.08079417,0.04427687,0.04110836,0.04812812,-0.053979542,-0.004387368,-0.04829328,-0.022975856,-0.015012431,-0.0056774826,-0.03936704,0.023132714,-0.007810687,-0.011018049,0.031620245,-0.02713872,0.0018347959,-0.024968592,0.02253628,-0.00809666,-0.0076680584,0.06435103,-0.020083368,-0.0049473317,0.07430767,0.01915259,0.040656384,0.00998682,-0.014684721,0.026354978,0.032759093,0.037668057,-0.009659323,0.006720873,0.063525185,0.03982695,0.04567435,-0.02619304,-0.030550981,-0.014520635,0.0010599799,0.034034356,0.06294083,0.07422565,0.01973267,0.05249243,0.010003681,-0.034319345,-0.023254821,0.0019625498,0.033209592,-0.015176091,0.056498263,0.0041291295,-0.046049923,0.054690883,-0.021583585,-0.019928787,-0.010311507,-0.03155074,0.038876258,0.055084117,0.0006716143,-0.005959439,0.02702423,-0.0041947966,0.015374709,0.057063535,0.028639654,0.069971144,0.019529812,-0.026227735,-0.083985895,-0.0041349265,0.009833876,-0.015811538,0.016993256,-0.010458223,0.040068664,0.009195164,-0.03924835,-0.007896623,-0.06261605,0.015779363,-0.018634042,-0.0013783163,0.016493134,-0.041971806,-0.039205268,0.020863583,-0.00169911,0.026609324,-0.07237093,0.07898098,-0.008871385,0.017599586,0.018514562,-0.01763139,0.00015460308,-0.03443664,0.026305566,0.0019577034,0.049758997,-0.014016935,0.01580608,-0.005885855,-0.014773614,0.008331391,0.011858725,0.047954902,0.016360788,0.040261615,-0.014324732,0.062151354,-0.037888777,0.02075746,0.039549813,-0.077434056,0.00096539775,-0.044017132,-0.012209571,0.034755055,-0.020098051,0.008095624,0.031291816,0.04792529,-0.008659437,0.01759492,0.009537845,-0.05313831,-0.010890252,-0.03342564,0.061369378,-0.031681072,-0.053262327,8.374469e-05,-0.027414132,-0.013404388,0.033906803,0.025408141,-0.035230264,0.030235829,-0.0014981066,0.023731904,0.029274339,0.047021322,0.025153603,-0.050763946,0.042003185,0.028869675,0.023947056,-0.045773767,-0.029348088,-0.04498305,0.03974547,0.021556387,0.032411546,-0.028107764,-0.01917967,0.020117322,0.035401057,-0.087708965,0.028180089,-0.07627729,0.010020432,-0.055026818,0.013467507,0.05156387,0.030606749,-0.012557438,0.0075980667,-0.049580842,0.025251655,0.011958476,-0.05784425,-0.00688397,-0.026897762,-0.0073929257,-0.082809925,0.0707716,0.0044888635,-0.023634167,0.00959699,0.027249858,0.009045479,-0.008601681,0.007323367,0.014609572,0.007073427,0.0055342577,-0.047172364,-0.023501316,-0.03593993,-0.022744065,-0.031178312,0.007601522,0.01038201,-0.040641543,-0.02084411,-0.04739785,0.0016813428,-0.022378212,-0.024991153,-0.019224035,0.033300195,0.04363394,-0.0072962623,0.0044990415,0.00530943,-0.0061862995,-0.1226422,-0.0048183375,-0.010383665,-0.043834127,-0.010673082,0.00016926302,0.026351877,-0.03451933,-0.017912712,-0.06287377,-0.00329357,0.056648213,-0.005951308,-0.017310314,0.06057505,0.00529039,0.04522765,0.009986563,0.09290384,0.0046436884,-0.027085476,-0.0051616537,0.014926508,-0.027059292,0.07819409,0.0018491915,-0.034066174,-0.04200668,0.017987153,-0.054097146,0.0263208,-0.030290576,0.012135319,-0.053635724,0.0040904377,-0.06391213,-0.012962556,0.039401833,0.029892938,-0.010509396,-0.09667328,-0.004525119,-0.0660734,0.005074788,-0.0043580704,0.048569698,-0.029491736,-0.00813117,0.099913284,-0.02152916,-0.0046480033,-0.004279434,-0.022350302,0.07403285,2.6268553e-05,0.024700351,-0.070556544,-0.046257928,0.047623277,0.013440511,0.022684522,0.0105078975,0.029062217,0.036317576,0.012476447,-0.025555858,0.0043436335,0.006260482,-0.030046312,0.012665346,-0.060015686,-0.042867333,-0.043334395,-0.09350731,-0.015882127,-0.023036648,0.0035012013,0.019168707,-0.029792963,0.014690395,-0.03232301,0.04318316,-0.023454774,0.024906443,0.033632547,0.026205529,0.021056164,-0.014863617,0.03884084,0.019737227,0.0643725,-0.015622061,0.010209574,-0.042415053,-0.041623153,0.020822845,-0.020490937,-0.0542278,-0.0033205135,-0.041752372,-0.069488324,-0.016277319,-0.0044792043,-0.02016524,-0.03959827,-0.032634977,-0.0039365673,-0.0132405395,0.0067148125,0.075648956,-0.05606617,-0.06265819,-0.019359354,0.05813966,-0.01447109,-0.010593954,-0.00086784246,0.00957173,0.02843471,0.00845407,0.024766237,-0.017594881,-0.02089351,-0.023622723,-0.033868976,0.01189866,0.04348284,0.017560178,0.0044504236,0.0201572,-0.010445271,0.016996963,-0.063251264,0.036506347,0.014985517,-0.004923813,-0.019643096,0.004065921,-0.03441569,0.02174584,-0.022037273,-0.105745554,-0.017520802,0.024135107,-0.056571614,0.065653384,-0.11961944,-0.019004421,-0.048515763,-0.018267322,-0.02178645,-0.00048087785,-0.042244278,0.041203473,0.039137937,-0.028382456,0.0027469762,-0.035103243,-0.008536376,-0.022003518,0.013834031,0.04035347,-0.05127768,-0.021083988,-0.019288905,0.030957388,0.03837377,-0.0003459004,-0.043197013,0.059090964,0.03584024,-0.009635979,0.049144205,-0.113005035,0.012198436,0.0030250824,-0.0005766731,0.010016404,-0.004630926,0.036304604,0.030682925,-0.028248072,0.0053004674,-0.028463472,-0.045950726,-0.016214147,0.02234844,-0.024365503,0.0045087263,0.0015641076,-0.046219032,0.019860927,0.011021814,-0.024108216,-0.048900776,0.012885111,-0.0022583513,-0.030102832,-0.016490621,0.024889058,-0.0009473834,0.015075038,-0.040798195,-0.005642347,-0.0029682147,-0.050329093,0.0009567131,0.007919075,0.01719906,-0.018685095,-0.016243592,0.010302834,4.1979074e-06,-0.042400364,0.055864133,0.033395868,-0.017874744,0.0013070442,-0.05331383,-0.10789571,0.0074728676,0.03525642,-0.07436872,0.04979144,0.046753135,0.0027637088,0.014162893,-0.026069263,0.06226656,-0.056384422,0.008216318,-0.02018645,-0.007397228,0.0074180462,0.035483476,-0.01882623,-0.02706421,0.04596009,-0.013163229,-0.021003753,0.037058793,0.052453898,0.013129776,0.015402059,-0.048313417,0.023352273,0.009391176,-0.044023603,-0.0107533,0.054881006,-0.019277383,0.02055352,-0.030710667,-0.02347742,0.0092705265,-0.047558293,-0.024285497,-0.03519891,-0.0038767713,-0.005330039,-0.026968258,0.06881978,0.06537581,-0.023353418,0.01331013,0.045053896,0.032502707,0.065926,0.0009946732,0.051750924,0.005718337,-0.0038732293,-0.029579317,-0.06977859,-0.0048092776,-0.025378013,0.023722455,0.032475006,-0.031788938,-0.00917764,0.0056064464,-0.016738426,0.021969007,0.012666437,-0.046921335,-0.02513667,-0.028311022,-0.009224157,0.05264038,-0.026426777,0.02599612,-0.018745475,-0.015264339,-0.013577108,0.0011754846,0.020499794,0.01423578,-0.015937831,-0.034813095,0.06295408,-0.033208452,0.041733917,0.0022288205,-0.0036853347,-0.015074669,-0.00813031,-0.004992453,0.010502773,0.017247686,0.03162546,-0.006212466,-0.06321386,0.022924462,0.03354761,-0.02742972,-0.018287206,-0.05058406,-0.02762529,0.014693771,-0.009422438,-0.0113650765,0.04500726,-0.009418481,0.023177318,-0.0394831,0.07899207,0.010970399,0.01519068,0.060208563,-0.014248415,-0.027108915,-0.055970594,-0.05615517,0.00082430604,-0.02946103,0.012972071,-0.034580585,-0.092063755,0.023562009,0.09187191,0.03979375,-0.048233856,0.0891921,0.0054705814,-0.07132956,-0.03294508,0.015985591,-0.06979576,-0.008607954,0.03748406,0.018775256,-0.00055046624,-0.0018972756,0.010640039,-0.039262787,0.045647603,-0.052634962,-0.04485457,0.059673585,0.005487001,0.005677175,-0.040526956,-0.0023886457,-0.051557075,-0.026969707,-0.020169057,0.020184118,-0.06750348,0.014797761,0.043389246,0.022667736,0.012956063,0.056346934,0.038232267,0.02334661,-0.002965094,0.053386245,-0.016282998,-0.08433834,-0.005240998,0.020763554,0.0041468525,0.011248255,0.013354228,0.0062226793,0.01238483,-0.042322755,0.017076539,-0.024617095,-0.03331688,-0.001430632,0.05623171,0.0073584137,0.013339925,-0.0041607106,0.015201854,0.029444456,-0.039367896,0.032675862,0.016636375,0.04101005,0.0073330533,0.03937178,-0.01699229,0.026922127,-0.00465699,0.014691186,0.07985071,-0.045738634,-0.040622048,0.040370528,-0.0070402357,-0.048223954,0.048428483,-0.013764062,0.02645368,0.030109879,-0.01834218,-0.0045400057,0.036011115,-0.010352046,-0.068165384,0.037795525,-0.036501475,0.020713413,-1.2293508e-05,-0.00038850267,0.073334076,0.01821627,0.003559663,0.017506005,-0.02564981,0.039007656,-0.026543219,0.018282859,-0.038226757,-0.04996024,0.01010447,-0.012900636,-0.020180488,0.042488355,0.0135185765,-0.0083626835,-0.019743606,0.025633369,0.035687257,-0.053833067,-0.053783447,0.007418253,-0.04581871,0.032362275,0.050387084,-0.010103674,-0.051880397,0.010476682,0.015898407,0.04970622,-0.04664034,0.036457486,-0.017625386,-0.0058598807,-0.011529857,0.018154921,0.013366902,0.0021690137]::vector(768)),\n", + " ('Coffee Maker', 'Brews coffee in under 5 minutes', 99.99, 'Kitchen Appliances', 20, 'SKU12347', 'https://example.com/coffeemaker.jpg', '{\"category\" : \"Kitchen Appliances\", \"name\" : \"Coffee Maker\", \"description\" : \"Brews coffee in under 5 minutes\"}', ARRAY[0.025002815,-0.052869678,-0.010500825,-0.024296444,0.049798742,0.043427017,-0.01307104,0.0077243242,0.022190414,0.037746448,0.029453197,-0.009484218,0.0028156517,-0.03531512,-0.012121426,0.0091221025,0.025652027,-0.009445565,-0.02820549,-0.04105274,-0.0010839493,0.015024874,0.053036522,-0.018628811,0.014746092,-0.049109433,0.026801802,-0.0070828577,-0.02369395,0.010975214,-0.03531074,0.04859645,-0.004710616,-0.018579654,-0.0076328423,-0.030808363,-0.012824788,0.03848257,0.014652247,0.058704656,0.00325119,-0.007205416,-0.04686223,-0.028575234,0.02045449,-0.008556303,-0.009746742,0.018289749,0.00093424425,-0.046003163,0.0039943205,-0.023993168,0.05866197,0.008093339,-0.00565744,-0.008198263,-0.001283407,-0.0007927462,-0.018114842,-0.008134085,-0.00014443924,0.021404255,-0.014830747,0.050932012,-0.032427747,-0.027500387,-0.020814912,0.025367612,0.061494272,-0.028271751,-0.002093295,-0.005629965,0.054627255,-0.062579386,-0.01051155,-0.06421958,-0.012094066,0.06576773,0.05998704,0.10272862,-0.021875817,-0.062225047,0.022178214,0.010618126,-0.05723891,0.040955715,-0.038523626,0.021909224,0.018677043,0.056335997,-0.01599579,0.015702266,0.025712736,-0.024550503,0.041618552,0.031751215,-0.0013378685,-0.042116627,-0.033073347,-0.011056941,0.022297822,-0.052519917,-0.06455736,0.030026494,0.04122688,0.0435459,-0.021909805,0.025392938,-0.05491582,0.022167888,-0.06104317,0.021199005,0.021531114,0.0003258208,0.051008765,-0.0056826724,0.0019850046,0.08186525,0.014742098,0.01913513,-0.026228607,-0.023587128,0.041640177,0.016765678,0.028365733,0.057187237,0.011515794,0.0734812,0.048084594,-0.0028821004,0.00025123838,-0.010272774,0.025670059,-0.049766205,0.0862307,0.07104121,0.008422137,0.026603732,0.06897059,-0.0013259795,-0.003537648,-0.016978277,-0.03289158,0.019160148,-0.030429484,0.03210423,-0.0025404708,-0.052619252,0.0020272017,-0.014941184,0.0026864705,0.012819193,-0.043763664,0.057997666,0.043563023,0.03174006,-0.04444913,0.0060016355,-0.029776296,-0.017748147,0.036185395,-0.0014833601,-0.017309692,0.04368944,0.020283954,-0.0160715,0.03019354,0.02680017,0.013467745,0.010598811,-0.009857402,0.0035379697,-0.04074403,-0.015414817,0.016311716,-0.0669727,0.0034562463,-0.024640094,-0.023524309,0.0028607736,-0.06249814,-0.058054965,-0.007223816,0.012088017,0.029124737,-0.030978883,0.07969112,-0.05076358,0.015344627,-0.00898595,-0.0097088795,-0.019155432,-0.035673082,0.027780814,0.006400352,0.055502266,-0.046420977,0.03919276,0.040964182,-0.024075434,-0.014520242,0.07941375,0.023109328,0.030869437,0.06598536,0.00059927267,0.076354064,-0.048273984,0.0025508753,0.0066330666,-0.070879966,0.03704847,-0.0650441,0.01176703,0.033744898,0.0400285,0.024317512,0.028281165,-0.008897873,-0.029537052,-0.0047060223,0.026686963,-0.07052627,0.023556747,-0.056385886,0.0714133,0.007949809,0.011887155,0.0029032454,-0.015065537,0.011513513,0.050219424,0.010533179,-0.009971522,0.03655571,-0.0066924663,-0.012303563,0.016773308,0.013691093,0.025839401,-0.044451136,0.049260832,0.05713467,0.013278825,-0.022100078,-0.0017930771,-0.016181005,0.0217466,-0.02600776,0.046996534,-0.022629611,-0.023503313,0.0074507482,0.0134722935,-0.04945182,0.022608835,-0.026130896,-0.01177188,-0.027667308,0.026118958,0.0025001818,0.021639917,-0.015105975,0.02968347,-0.043928802,0.03762012,0.019912925,-0.004347233,-0.006596504,0.016333994,-0.025137693,-0.01686705,0.04786869,0.034643404,0.011117003,-0.011134983,-0.0074818125,-0.006335571,0.022040822,-0.006491301,0.0054816976,0.038022403,0.016072717,-0.06609374,-0.03203102,-0.059326455,-0.04408214,-0.03787348,0.014894112,-0.02038928,-0.044823527,-0.015866352,-0.047105137,0.002020473,-0.04468357,0.018793538,-0.029475007,0.06967502,0.04684481,0.048074055,-0.0010090554,-0.0027273456,0.047790546,-0.030050496,0.023022242,-0.028264726,0.03571066,-0.0164874,-0.019399788,0.0076415916,-0.0060172956,-0.010469042,-0.045296766,-0.0071801674,0.032818798,0.034934863,-0.0737483,0.0327411,-0.006032433,0.05928009,-0.00927453,0.07627373,0.0050010816,-0.048511107,-0.0037969523,-0.007150538,-0.010152546,0.025746513,-0.02757783,-0.049112115,-0.029450508,0.037618097,-0.04765299,0.021502782,0.04031621,-0.021789404,-0.03477437,-0.0029428764,-0.04645585,0.015724704,0.0061205924,0.027327916,-0.016831782,-0.07413835,-0.009106179,-0.005994898,0.0015746661,-0.0066348854,0.08860898,0.026653405,-0.010490873,0.01737892,-0.036203787,0.0019658727,-0.05349199,-0.031604912,0.059320047,-0.0035595773,-0.013159466,-0.043662973,-0.000936755,0.037883844,1.1725969e-05,0.008455511,0.028007427,0.026448535,0.03587197,0.034501214,-0.020195212,-0.036874935,0.008322776,-0.038275808,0.014955824,-0.066956565,-0.03433901,-0.043297864,-0.07503335,-0.037108134,0.032691672,-0.05912909,0.023559488,-0.023238983,0.0012042256,0.0074822125,0.0058873207,-0.021845229,0.0054280413,0.05752058,-0.026954507,0.026175871,0.012301664,0.06307251,0.07353519,0.011740042,-0.012562488,-0.025707787,0.011014364,-0.064245604,-0.018075097,-0.04286179,-0.06992585,-0.031043975,-0.0022823277,-0.05855018,0.015864456,0.00024379989,0.0070141326,-0.00035948,-0.023150876,-0.063177474,0.008194795,0.023019124,0.014603101,-0.06850171,-0.07586402,-0.029384725,0.09732399,-0.023403296,0.00983274,0.00043465907,-0.037277438,0.060318034,-0.010698135,-0.0012939094,-0.015873678,-0.006272459,0.0014064384,-0.041425075,-0.021238888,0.021737115,0.030599548,0.043125883,0.01929081,-0.0011234619,-0.031159677,-0.05745639,0.0146679375,0.046521254,-0.01835481,-0.033141162,0.00036415283,-0.06466151,0.043580752,0.011921412,-0.07292401,-0.047980927,0.02159395,-0.023352068,0.0425091,-0.09635663,0.0060955146,-0.06484201,-0.029811602,-0.026076958,-0.014945281,-0.04334233,-0.00242451,0.047840517,-0.02103297,-0.0191666,-0.0074735563,1.0544848e-05,-0.028074,-0.037163526,0.030064873,-0.02934737,0.050285384,-0.023986174,0.025914317,0.10199452,-0.021887174,-0.0066847154,-0.023618985,0.03283886,0.045797225,0.047762897,-0.07030183,0.026901271,0.008702326,0.017019885,0.033345792,-0.03833666,0.031567782,-0.013102635,-0.009532979,0.025451964,-0.021708276,-0.023218581,-0.07980661,0.03028782,-0.021675726,0.03096571,-0.018742265,-0.04427001,0.009433704,0.03455316,-0.035231985,0.04002238,0.012793141,0.025124295,-0.04512409,-0.06486318,0.019942157,-0.030111039,-0.0069209165,0.0015545462,0.028818183,0.0014206765,-0.032698274,0.008883163,0.058960456,-0.00906729,0.0298577,-0.0070162034,-0.014469902,-0.0032146918,-0.04448409,0.03293327,0.040138587,0.01842061,0.0055912337,-0.03388838,-0.071546026,0.02821449,0.033089,-0.04839594,0.016159212,0.08211776,-0.08987595,0.036964364,-0.051373526,0.1035708,-0.053108595,-0.01896186,0.01644011,-0.012502358,0.008263514,0.04065409,-0.015298684,0.0011162056,0.04276282,0.0027434586,0.0324373,0.03511016,0.02446925,0.002442109,0.049384676,-0.05747281,-0.0020478321,-0.03639974,0.011938583,-0.031114291,0.03284646,-0.03238849,0.08670559,-0.07415254,-0.036738325,-0.025126172,-0.045095183,-0.015307702,-0.06554373,-0.05546525,0.005472855,-0.006981692,0.04587679,0.111925036,0.013912294,0.014268016,0.058842134,-0.011192024,0.034922387,0.012045642,0.008008024,-0.014226386,0.06913233,-0.04700873,-0.06164794,-0.0024386728,0.043209903,0.051432677,-0.017323477,0.013788927,0.012737198,0.06472892,-0.070449375,0.005222667,0.050599333,0.0015403829,0.015714316,-0.008632714,0.014941663,0.06433311,-0.021354778,-0.0071928906,-0.028242689,0.018915592,0.021451298,0.0063637616,0.0019523413,-0.017883593,0.028570741,-0.016318232,0.053636383,-0.028484613,-0.006531752,0.022900375,0.023723338,-0.024363475,-0.015181002,0.024642847,-0.002409233,-0.0001194501,0.013567875,-0.046026736,-0.016705032,-0.013025837,0.020370122,-0.027258568,-0.04735096,0.011894463,0.0019317217,-0.0031460563,0.040866848,0.00464604,0.03964947,0.027275842,-0.0030081465,-0.008669969,0.0462421,0.010375526,-0.024637504,0.08480695,-0.02768799,-0.005021901,-0.009944692,0.015040328,-0.0051919715,-0.043738216,0.054622557,-0.0116185825,-0.044851393,-0.01769878,0.06967592,-0.026938388,0.0030814619,0.07516173,-0.022243993,-0.09390373,-0.056307606,0.011178256,-0.058882743,0.016906237,0.010931337,0.011277608,-0.03310829,0.008875099,-0.017342865,-0.049926963,-0.0021014255,-0.019715691,-0.024091842,0.029629463,-0.06452303,0.009643791,-0.025999011,-0.017722748,-0.09347366,-0.019748896,-0.011190205,-0.0044534663,-0.04336357,-0.01312215,0.056558847,-0.022783643,0.0004763564,0.04152026,-0.03813543,0.0038315274,0.021157283,-0.007934057,0.0004752217,-0.057082873,-0.011285772,-0.014152046,0.03181829,0.033805694,0.04453719,-0.02024123,-0.0038247174,-0.0262423,0.007036252,-0.012817323,-0.025822328,0.06599188,0.067939,-0.022174655,-0.022773167,9.6714546e-05,-0.017627345,0.08549309,-0.06266334,-0.00575442,-0.011873023,-0.07250961,0.0056728884,0.017012162,-0.025071641,0.022021066,0.030550413,-0.010627088,0.050028834,-0.01721913,-0.050976366,-0.024867795,0.011782799,-0.075504154,-0.004392594,-0.01807583,0.031157117,0.030725744,-0.014750008,0.005684259,0.047403537,-0.08811708,0.007985649,0.043377616,-0.037903026,0.029741386,-0.0011720062,-0.010578729,0.051289707,-0.024345556,0.017949736,0.02636295,-0.059689533,0.06373776,-0.049072567,0.013506145,-0.040476285,-0.02940512,-0.023568999,-0.00035632766,0.056101788,0.061561547,0.03079068,-0.02166795,0.009211557,0.0030255727,-0.0036865661,0.023821775,0.0015869564,0.0064414316,-0.057368714,0.061502002,0.023947174,0.0046180966,-0.05202509,0.002360597,0.03557417,0.036739,-0.03005605,0.047780115,0.025282156,0.034349978,0.034781702,0.0276351,-0.040908,0.081558466]::vector(768)),\n", + " ('Bluetooth Headphones', 'Noise cancelling, over the ear headphones', 250.00, 'Accessories', 5, 'SKU12348', 'https://example.com/headphones.jpg', '{\"category\" : \"Accessories\", \"name\" : \"Bluetooth Headphones\", \"description\" : \"Noise cancelling, over the ear headphones\"}', ARRAY[0.022783848,-0.057248034,-0.047374193,-0.04242414,0.049324054,0.0077371066,0.017048897,0.00500827,0.008471851,0.010170231,0.054357704,0.018568166,-0.024179503,0.026519066,0.026404649,-0.06330503,0.014405935,-0.015520485,0.0052459002,-0.0398403,0.0026082278,-0.026374431,0.020055598,-0.009738811,0.013321584,-0.033184614,0.034118295,-0.0011876881,-0.04513898,0.04878162,-0.0725106,0.018109042,-0.075869314,-0.023766529,0.015067321,-0.019572936,0.024169574,-0.01577634,-0.048197363,0.049358875,-0.030935159,-0.0363981,-0.04534119,-0.044748895,-0.004167742,-0.02121328,-0.052715167,0.0006209187,0.036595955,-0.085123576,0.052309636,-0.01926014,0.00049565616,-0.0057477825,0.010993081,-0.06675727,0.0037074706,-0.033420403,-0.052601676,0.023439946,-0.01880516,-0.009576131,-0.0114066675,0.10504714,0.00022831495,0.029810086,-0.0044366047,0.043377023,0.06093195,-0.004545408,0.013371212,-0.029174658,0.06625106,-0.0077476054,-0.0163617,-0.056035727,-0.024698364,0.06076837,0.020102862,0.038081013,-0.018504761,-0.027918378,0.03942784,0.004596525,-0.057653908,0.034515597,0.010063118,0.04525672,0.023651283,0.03596632,-0.0378574,-0.013078957,0.021554954,-0.0606351,-0.007272484,0.044470455,-0.015513987,-0.018171282,-0.014020262,-0.040379126,-0.032836802,-0.055859733,-0.05644243,-0.001610613,-0.05527219,-0.00052593346,-0.00546389,0.02911079,-0.0037673921,0.036246333,-0.057133533,0.043779045,-0.0028422247,-0.044305976,0.05993566,-0.005543668,-0.0015800337,0.07515586,-0.00020748413,0.03876171,0.026035579,0.012980581,0.056657698,0.020252425,0.029382393,0.011205804,0.039896134,0.04349186,0.08402962,-0.0031059172,-0.022395832,-0.023471512,0.029480197,0.0038065156,0.07106566,0.07560159,0.019708911,0.0063190344,0.06826459,0.05426478,-0.016353253,-0.016603524,0.035430502,0.01285351,-0.044608854,0.06445639,0.027575186,-0.020047447,0.07155171,-0.024042875,0.007684551,-0.057774883,-0.05863421,0.04027459,0.034241315,0.029786138,-0.011771758,-0.008067332,0.005154275,0.017256541,0.012795448,0.0361206,0.046198364,0.007581977,-0.0643159,-0.032997373,0.025989803,0.039828006,0.00950064,0.043332074,0.016609278,0.034839373,-0.022875424,-0.028605282,-0.017703732,-0.06238004,0.010994231,-0.0007306017,-0.034711856,-0.0440203,-0.025970237,-0.04595589,0.030582627,0.0073314123,-0.017986864,-0.055571377,0.082270294,-0.018736921,-0.0012149982,-0.0060279733,0.0044796504,0.025173035,-0.037219252,0.00027956237,-0.010430433,0.02825617,-0.046855696,0.018841878,0.0435598,0.005803966,0.0019149927,0.092197396,0.022937872,-0.0033373323,0.072473325,-0.014439769,0.047117453,-0.08000118,-0.012863106,0.0260884,-0.04135028,0.0070068296,-0.07510927,0.03672727,0.033531025,0.042364623,-0.019229556,0.0048453975,0.031276144,0.014006409,0.016036421,-0.017694592,-0.036794797,0.014908425,0.030831292,0.03190712,-0.022060342,0.041704472,0.017002491,-0.06408182,0.03923344,-0.02587273,-0.017719302,-0.025430005,0.06814103,-0.009046621,0.033220492,-0.033640996,-0.02523642,0.048086986,-0.035158273,0.048114188,0.043751266,0.01995209,-0.0295469,-0.020247698,-0.053099316,0.032099206,-0.045260355,0.0326798,-0.0043251985,-0.052964494,0.07017924,-0.0037189184,-0.03395965,0.040903587,-0.060891,-0.010537573,-0.030650055,-0.029651405,0.013975478,0.007255845,-0.010439494,-0.011794211,-0.05466926,0.024609366,-0.017408509,-0.05243266,-0.020957882,0.037831362,0.0216147,-0.035116594,0.03829302,-0.016048789,-0.035066966,-0.013764898,0.00042713518,0.030633073,-0.008326726,-0.015224956,0.012373721,0.0844943,0.0245434,-0.046264216,-0.011655971,-0.013199105,-0.05529712,0.006216126,0.038966317,0.04622981,-0.039118554,-0.044550307,-0.009771392,-0.006652356,-0.023040479,0.010476257,-0.004093151,0.008969803,0.010324751,-0.022387082,0.023577597,0.019100022,0.008391375,-0.07391311,-0.02210422,0.021720598,-0.0109519595,-0.0820701,0.022086475,-0.003670014,0.0019491176,-0.053155318,-0.022906458,0.0148452455,0.015515676,0.019605495,-0.02868708,-0.01828674,-0.0005499542,0.06639364,-0.01821442,0.09175476,-0.0016622626,-0.059729476,-0.019477114,0.025505545,-0.034742665,0.028956799,-0.019135797,-0.016046764,-0.03779796,0.06325585,-0.04046284,-0.0065921973,-0.0019740656,0.053527426,-0.06304376,-0.035805233,-0.04792203,-0.0012729234,0.048093352,0.007456611,-0.058022104,-0.07442454,0.012629627,-0.027595298,0.0021199721,-0.027464667,0.02698153,0.00060683774,0.044545636,0.06083593,-0.0031620082,-0.025901018,-0.034706157,0.013555886,0.042545,0.056980383,0.009854132,-0.06190446,-0.034308147,0.0043845526,0.017239122,-0.031214224,-0.010807414,0.026710719,0.022394834,-0.009421089,-0.04236166,0.022885358,0.01318956,-0.019174583,-0.0026612883,0.010784672,-0.010333064,-0.043234736,-0.054500565,-0.027753199,-0.022639737,-0.03062474,0.008183766,-0.017117208,0.03024305,-0.03615811,-0.01150264,-0.03863528,0.04852956,0.024548976,-0.012997513,-0.0041008275,0.03406041,-0.0070994645,0.072934166,0.02805505,-0.030694276,-0.035828616,-0.017640414,-0.03957751,0.06840472,0.0046152286,-0.020437988,-0.025648775,-0.083415866,-0.04167123,-0.035016168,-0.015291769,0.009293348,0.04628708,-0.014721913,-0.0033228637,0.04403616,0.061276685,0.037830554,-0.041214965,-0.084479295,-0.0012414041,0.030978376,-0.017235488,0.04445431,0.05231969,-0.0008037167,0.045372415,0.02067265,0.024952972,-0.033815585,-0.03739797,0.034983158,-0.016312862,0.017926387,-0.02016297,-0.019343764,0.017820694,-0.011671569,0.02410841,-0.042012513,-0.03900872,0.032663334,0.011938514,-0.029834026,0.047740217,-0.0058686035,-0.046729274,0.05985927,0.007610642,-0.060446266,-0.04216537,0.017497085,-0.06986214,0.076023735,-0.10476386,-0.020937927,-0.073560745,-0.014322972,-0.048601817,-0.0056885225,-0.03637434,0.04715089,0.054749545,0.014689732,0.006048463,0.046543427,-0.017363597,-0.03678888,-0.08802858,0.063708976,0.021423126,0.04030153,-0.036243204,0.036450744,0.024569608,0.016401349,-0.022465378,-0.0034262848,0.060547307,-0.014745138,-0.020591581,-0.0054737274,-0.0074623367,0.06138278,-0.016604895,0.0032445828,0.009028142,0.002864045,0.001341044,-0.03825005,0.03237135,-0.009647875,-0.0470159,-0.024240978,0.017859152,0.010279892,-0.014414872,-0.017152937,0.020384172,0.008366546,-0.003495199,-0.024638942,-0.031768326,0.06240018,-0.0067493794,-0.04322142,-0.0030645356,-0.0027114467,-0.0072583677,0.06152745,-0.05525731,0.01201016,0.034348775,-0.032004267,0.027236925,0.05926736,-0.010569189,-0.023563573,0.0018119658,0.04231199,0.01966649,-0.014960187,0.029649874,0.01606933,0.0033748902,0.021692606,0.00794783,-0.113133654,0.012736659,0.03742399,-0.010987754,0.02547777,0.026347551,-0.09020402,0.009588993,-0.043276373,0.106708415,-0.049185734,0.007007848,0.030148245,-0.026434064,-0.017702276,0.0007948643,0.026716268,0.013030543,0.013651695,-0.019745413,-0.0087912055,0.0046337135,0.038207695,0.0059329793,0.016351476,-0.069271706,-0.006662047,0.028512167,0.0038343759,-0.00027066944,0.042824432,-0.038911343,0.012483791,-0.06324616,-0.0023198558,-0.0028892683,-0.043326154,-0.035926916,-0.006348816,-0.025913015,-0.015930604,0.040185526,0.044628017,0.039083507,0.009474702,0.017115341,0.05052131,-0.01357451,0.020331299,0.038159154,0.0349774,0.015846666,0.022699736,-0.022343196,-0.056707054,-0.0010885954,0.020071063,-0.000391925,0.06397024,-0.024627347,-0.005184313,0.05034518,0.009061781,0.034097236,-0.007981921,-0.03801412,-0.0028578758,0.013567372,-0.008190868,0.033633735,-0.053976685,-0.025468381,-0.044378527,0.032747604,0.036202736,0.038062613,0.014995585,0.0036792904,-0.01603846,-0.047275733,0.066113465,0.0045884387,0.08915791,-0.0068142917,-0.0064188805,-0.060516927,-0.016080644,0.041549493,-0.008397882,-0.0071816393,0.0064753946,-0.0017467311,-0.019128935,0.0164788,0.022168875,0.011003241,-0.026863558,-0.05437178,-0.032724023,-0.0042122444,0.010392475,0.0042387135,0.04948556,-0.013747793,0.051330764,-0.0050607547,0.054571416,0.025556272,-0.00022029632,0.047628347,0.01427685,-0.020254403,-0.03590239,0.011610469,0.041846078,-0.02470694,-0.013697807,-0.021193847,-0.04341633,-0.0041078446,0.053439837,-0.021625757,-0.037942924,0.07774385,0.005912317,-0.0929516,-0.025328774,0.025199909,-0.041145753,0.017296704,-0.0050417483,0.012186051,0.0024183579,-0.025558045,-0.0051468383,-0.07548276,0.028603543,-0.04549798,0.007635448,0.010916566,-0.029269122,0.01546215,-0.024502348,-0.021702306,-0.025917016,-0.016031386,-0.0012059321,0.031981774,-0.056502126,0.025166377,0.04160211,-0.020680273,0.010293909,0.029529357,-0.01568588,0.026115898,-0.01032236,0.02089118,-0.01709118,-0.0597839,-0.029326,0.045068808,0.00761455,0.034416553,0.022160128,-0.025166225,-0.04248117,-0.029465536,0.027829373,0.006342403,-0.05032602,0.040032476,0.07705231,-0.01583979,-0.0049204687,-0.0140532,0.022657644,0.05293866,-0.009608256,-0.005003684,-0.02478457,0.029608795,-0.022698086,0.003895031,-0.0039730286,0.023749523,0.07514458,-0.036099195,0.04289709,-0.02329434,-0.06419562,0.037709154,0.0004289863,-0.02686404,0.0032855049,-0.030955583,0.018836787,0.033646755,0.022193655,-0.028475504,0.00394302,-0.05765806,-0.062945105,0.024937302,-0.06828151,0.016094193,-0.036405172,-0.016450962,0.04907368,-0.05975235,-0.04858766,0.07675482,-0.06289323,0.052024625,0.018600732,0.038572676,-0.0011811757,-0.07612922,0.03844793,-0.015206284,0.05163554,0.046980042,0.023522004,0.00037627618,0.011324654,-0.028600419,6.0430884e-05,0.00431597,-0.023766082,-0.015001608,-0.018692184,0.08730754,0.032889076,-0.018612336,-0.019428827,-0.002722986,-0.020110032,0.04016962,-0.043657966,0.044247244,0.019661218,0.042629678,0.016911589,0.038489193,-0.0036892071,0.015036206]::vector(768)),\n", + " ('Backpack', 'Waterproof backpack with laptop compartment', 59.99, 'Accessories', 30, 'SKU12349', 'https://example.com/backpack.jpg', '{\"category\" : \"Accessories\", \"name\" : \"Backpack\", \"description\" : \"Waterproof backpack with laptop compartment\"}', ARRAY[-0.0028279827,-0.02903348,-0.02541054,-0.025740657,0.06572692,-0.01105207,-0.018005589,0.014476618,0.0039552255,0.04976717,0.034852527,0.018194634,-0.010718678,0.012003344,-0.008418802,-0.026018273,0.029329967,-0.016163627,0.009272989,-0.03639675,0.011046671,-0.008078595,0.023365447,-0.0033083789,0.020028763,-0.025491415,0.033595297,-0.0116388025,-0.057485484,0.06268812,-0.05302806,0.033510745,-0.06083909,-0.03115934,-0.014793818,-0.028653687,-0.011399838,0.03950949,-0.03437827,-0.001663737,-0.01088612,-0.01894241,-0.055767413,-0.0044360803,0.043946534,0.012161133,0.03891473,0.001239441,0.009908146,-0.07272227,0.055397917,0.003453955,0.016562339,-0.041937787,0.05197343,-0.026436094,-0.025229415,-0.034988422,-0.02628748,0.022921052,0.013600747,-0.0020118777,-0.033795673,0.06700571,0.016018055,-0.024256106,-0.02621731,0.045516666,0.05339654,0.0040287147,-0.03260985,0.0014520925,0.064204894,-0.07453437,-0.05054596,-0.042698923,-0.010596,0.013536595,0.0057951836,0.02499754,-0.008574824,-0.0074555897,-0.03567392,-0.016175417,-0.048651025,0.051804803,0.032162882,0.015001442,-0.015329716,0.028219966,-0.031235777,-0.011996138,0.001956758,-0.057833184,-0.022306677,0.031238675,-0.006414606,-0.06930158,-0.017475452,-0.027142663,0.020731354,-0.02221535,0.031049741,0.02081393,-0.022421336,0.0264318,-0.009509332,0.03522677,-0.004379289,0.011600757,-0.022017384,0.010730822,-0.010784208,-0.032706123,0.011207074,-0.023580823,0.013793131,0.05083659,0.047280807,0.048402432,0.05347524,-0.01837716,0.005956893,0.038448945,0.056967188,0.0107236095,0.03256511,0.06276655,0.04472847,0.04416061,-0.010116117,-0.048367113,0.029135885,0.010681488,0.036315914,0.056885246,0.03745567,-0.045721106,0.060501557,0.07454113,-0.018330548,-0.0113306865,-0.011580698,0.020741342,0.020118712,0.08663372,-0.009871896,0.0153012,0.05436686,-0.032210644,-0.029824084,0.023739373,-0.024163425,0.025129095,-0.016016128,0.04870382,0.013377057,0.012678613,0.011070294,-0.0072714896,0.042209458,0.029714484,0.042831365,0.032464053,-0.047759824,-0.032160178,-0.014084912,0.016434442,0.009782443,0.0013573115,-0.015243139,0.007621731,-0.037185922,-0.054615762,-0.008570435,-0.00029953485,-0.012346052,0.00016998274,-0.03163527,-0.0139267165,-0.07079747,-0.007061694,0.020720486,0.0025725542,0.019498186,-0.03700232,0.10145702,-0.004775887,-0.042089477,-0.023965659,-0.04021527,-0.0004672301,0.007410538,-0.0024715534,0.013863051,0.02261263,-0.027591249,0.020157337,0.012993745,-0.0067202765,-0.029478177,0.052134037,0.020799996,0.014809602,0.06626069,0.0069596902,0.063764,-0.04220143,-0.0040134583,0.007221788,0.014255095,0.059271786,-0.04741277,0.014235989,0.067689635,-0.005667792,0.03801926,0.0117749525,0.025480399,0.011015113,0.0037910545,0.00022392142,-0.044315543,0.010447604,0.010668871,0.0779741,-0.08010141,0.04994428,0.0024064495,-0.04755275,-0.0114773,0.014421721,-0.028229935,-0.06231835,0.05197635,-0.00798093,-0.0025467642,0.010583627,-0.017485484,0.048588324,-0.0008222008,0.033517472,0.007129084,0.0010124474,-0.05219366,0.017978905,-0.01833836,0.019664295,-0.008339645,0.013213594,8.404173e-05,-0.058585837,0.06634499,-0.032446846,-0.066239364,0.0011773852,-0.07504017,0.026009388,-0.026110237,-0.00089784985,0.004558591,-0.027107328,0.017480537,-0.0062587988,-0.008309775,0.024417007,0.022020336,-0.025295774,0.0089702625,0.026482984,0.008462929,-0.043885507,0.023143305,-0.012536918,-0.025114551,-0.030675266,-0.030063663,-0.004634334,-0.0024470752,-0.03869859,0.015594325,0.0131572345,0.0029243943,-0.046118148,-0.03834942,-0.022946607,-0.0071579637,-0.042097863,-0.01229437,0.024193348,-0.03535916,-0.05725744,-0.014191351,-0.034702625,-0.03553529,-0.0063754944,0.0024684118,0.042859882,0.013016258,-0.02985961,-0.0020391515,0.030625137,0.016144354,-0.049042817,0.024231678,-0.025589447,-0.05898161,-0.023193993,0.031626217,-0.028190944,0.017940147,-0.049932066,-0.04810013,0.047244985,0.1082508,0.001041191,-0.057233974,-0.006368648,-0.06945289,0.048442855,-0.021192377,0.10568124,0.053165488,-0.0084766345,0.031292096,-0.009400329,-0.042162478,0.06982496,-0.014560452,-0.0073914286,-0.048956916,0.030368945,0.022202695,-0.0050742053,-0.012722453,-0.011377622,-0.051865157,-0.0070718606,-0.01745792,-0.02462795,0.030636197,0.030104883,-0.04482826,-0.11079195,-0.024324121,-0.002861835,-0.014245193,-0.020608244,0.03153579,-0.009367316,0.014898636,0.033479474,-0.015162162,0.01307384,-0.052216247,-0.025208864,0.014302212,0.023454865,0.030064361,-0.00028293114,-0.05237653,0.02271106,0.0057998034,0.021696828,0.0065965196,0.061783127,0.052609395,0.018527359,-0.012383652,0.036548115,6.0759903e-05,-0.027102679,0.0020538126,-0.026467739,-0.00931995,-0.056754645,-0.059189495,0.022508893,-0.037084196,0.008752761,0.011397571,-0.001640177,0.010061019,0.024978038,0.01750796,0.0017406448,0.0692028,0.042931892,0.008515072,-0.03527143,0.006649334,-0.0015101181,0.09099013,0.0423155,-0.060909722,-0.007118597,-0.0070489836,-0.05583034,0.035233498,-0.008949495,-0.021592604,-0.023997912,-0.030185444,-0.015039309,-0.07469254,-0.05510056,0.029319923,0.01650634,-0.0660325,-0.015404232,0.03715267,0.03294396,0.005133208,-0.071616374,-0.04183193,-0.039515678,0.06556278,-0.006204309,0.018765671,0.0087025305,0.04139539,0.039423864,-0.0096283825,-0.03788884,-0.030308004,0.016888767,0.033892095,-0.0046063373,0.036512673,0.046478424,0.030432703,-0.008351917,0.038958482,0.030963391,-0.0012744869,-0.068324916,0.035514664,0.029101191,0.019952206,-0.035990257,0.05016547,-0.0034300084,0.011099454,-0.01642832,-0.055300374,-0.07178654,0.023697836,-0.02809622,0.054089297,-0.1083301,-0.018408947,-0.075191386,-0.0048826155,-0.042217527,-0.069461025,-0.06703293,0.009000863,0.06276143,-0.0017238993,0.03036515,-0.009982445,0.055421855,-0.027764114,-0.05543302,0.022685751,0.022210898,0.049183954,-0.0047965907,0.055648796,0.011152965,-0.014035957,-0.02337775,-0.01123261,0.052066986,-0.006916061,0.03199984,-0.094863154,0.003547006,0.041498255,0.004490882,0.020994756,-0.07455022,0.036187306,-0.0051827626,-0.017956927,-0.00029976605,-0.044009093,0.0028350798,-0.052361596,0.07876513,-0.06365592,0.0017824164,0.017088404,-0.038679466,-0.008001763,-0.0013830748,-0.025812596,-0.0182766,-8.765931e-05,-0.0072022257,-0.046436142,-0.072371304,0.0057044053,-0.03468649,0.056389496,-0.020051511,0.031401794,0.0026272596,-0.045338016,-0.029466175,0.008883405,0.036455907,-0.012484258,0.0015844881,0.036832172,0.023578366,-0.043958467,0.00577308,0.055652507,-0.036696434,0.002894534,-0.032786682,-0.05258521,-0.006260205,0.030400572,-0.061743345,0.021158593,0.028482735,-0.061397683,-0.015825676,0.01941984,0.075950265,-0.11372872,-0.018362995,-0.010228874,-0.009783626,0.023449693,0.027557475,-0.0023083165,-0.0021188299,0.05987247,-0.00944442,-0.020868102,0.03482851,0.039515875,-0.026193311,0.023197955,-0.07931663,0.005395495,0.013140455,-0.061495673,0.0022219154,0.038023517,-0.05545234,0.020771723,-0.0067305462,-0.03169365,-0.021337083,0.019638145,-0.053754907,-0.035756346,-0.036120877,-0.05413345,-0.0077516357,0.03129875,0.016264724,-0.011121187,0.016678393,0.0678958,-0.014889522,-0.019517552,-0.0059457496,0.018003179,-0.0072531863,0.081852585,-0.030259738,-0.05358454,0.020454926,-0.009424692,0.10091245,-0.012819172,-0.011656013,0.031110896,0.08538375,-0.026021762,0.047623295,0.04384129,-0.05093276,0.014624959,0.026958883,-0.004577614,0.02551685,-0.019736024,0.0063903728,-0.024696782,-0.041850932,0.027209712,0.0050771283,-0.028201208,-0.03125501,-0.001541728,-0.06142714,0.054404832,-0.007287412,0.0626698,0.03180891,-0.015927717,-0.04500077,-0.0022995493,0.0124429,-0.015138294,-0.026622217,0.008842311,-0.010787062,0.0010311591,0.0013770667,0.039663706,-0.02192414,-0.019322718,-0.051264115,-0.011981459,-0.03414706,-0.006800422,-0.028382706,0.043155897,-0.007300542,0.02638807,-0.019196216,0.06930381,0.020622948,0.014042502,0.06754253,-0.043790415,0.015294639,-0.040941276,0.028382495,-0.013607999,-0.040120583,0.008768077,-0.0101868035,-0.060808867,-0.013499631,0.059239235,0.035230562,-0.019976182,0.11870333,0.053272087,-0.08745547,-0.018802922,0.004555603,-0.028306624,0.0020639726,-0.018859716,0.026370116,0.0097041875,-0.0029847843,0.017317675,-0.0533067,0.038994376,-0.03322375,-0.052456018,0.050101582,-0.015041677,-0.03370439,-0.010739062,-0.039727744,-0.045931656,-0.08658831,0.05190126,0.055936754,-0.07664951,0.041408025,0.011245535,-0.012530026,0.024861438,0.016954603,0.017269976,0.06397909,-0.000105038154,0.036761504,0.006065827,-0.02139009,-0.025604198,0.010828613,0.023636553,0.04226646,0.041076142,0.025892248,-0.051934887,0.0029032188,0.040332098,-0.015436589,-0.057878137,0.005353198,0.064739525,-0.006427803,-0.024176747,0.011304507,0.03381613,0.08625095,-0.027353497,-0.039551895,-0.04934357,-0.016709028,0.024133967,0.00441431,-0.048314437,0.040782917,0.026620803,-0.02146332,0.030112874,-0.027528606,-0.016772546,0.005690125,-0.0047134855,-0.036793064,0.04092668,-0.02411072,0.023851473,0.07727627,-0.006492274,-0.0025583038,0.0017014288,-0.0541687,-0.010395329,0.031044465,-0.0536995,0.029957417,-0.040688735,-0.037072316,0.01663893,-0.04231374,-0.030213326,0.0061428403,-0.06634084,0.06036701,0.016658397,0.024410319,-0.03309207,-0.03735754,-0.04359427,-0.013476715,0.00078163255,0.033615876,0.022759296,-0.003551954,0.017715035,-0.0072518513,0.033236742,-0.0070533687,-0.05334901,-0.014660441,0.0025560227,0.03979979,-0.00433087,-0.018232862,-0.017161474,0.008870558,0.021989124,0.078787796,-0.009815632,0.022819351,0.020795409,0.028896132,-0.0061202813,0.012352534,-0.009014175,0.0024110335]::vector(768))\n", + " ```\n", + "
\n", + "\n", + "Here is how this table mapped to `PGVectorStore`:\n", + "\n", + "- **`id_column=\"product_id\"`**: ID column uniquely identifies each row in the products table.\n", + "\n", + "- **`content_column=\"description\"`**: The `description` column contains text descriptions of each product. This text is used by the `embedding_service` to create vectors that go in embedding_column and represent the semantic meaning of each description.\n", + "\n", + "- **`embedding_column=\"embed\"`**: The `embed` column stores the vectors created from the product descriptions. These vectors are used to find products with similar descriptions.\n", + "\n", + "- **`metadata_columns=[\"name\", \"category\", \"price_usd\", \"quantity\", \"sku\", \"image_url\"]`**: These columns are treated as metadata for each product. Metadata provides additional information about a product, such as its name, category, price, quantity available, SKU (Stock Keeping Unit), and an image URL. This information is useful for displaying product details in search results or for filtering and categorization.\n", + "\n", + "- **`metadata_json_column=\"metadata\"`**: The `metadata` column can store any additional information about the products in a flexible JSON format. This allows for storing varied and complex data that doesn't fit into the standard columns.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set an existing table name\n", + "TABLE_NAME = \"products\"\n", + "# SCHEMA_NAME = \"my_schema\"\n", + "\n", + "# Initialize PGVectorStore\n", + "custom_store = await PGVectorStore.create(\n", + " engine=engine,\n", + " table_name=TABLE_NAME,\n", + " # schema_name=SCHEMA_NAME,\n", + " embedding_service=embedding,\n", + " # Connect to existing VectorStore by customizing below column names\n", + " id_column=\"product_id\",\n", + " content_column=\"description\",\n", + " embedding_column=\"embed\",\n", + " metadata_columns=[\"name\", \"category\", \"price_usd\", \"quantity\", \"sku\", \"image_url\"],\n", + " metadata_json_column=\"metadata\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note: \n", + "\n", + "1. Optional: If the `embed` column is newly created or has different dimensions than supported by embedding model, it is required to one-time add the embeddings for the old records, like this: \n", + "\n", + " `ALTER TABLE products ADD COLUMN embed vector(768) DEFAULT NULL`\n", + "\n", + "1. For new records, added via `VectorStore` embeddings are automatically generated." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents with metadata filter\n", + "Since price_usd is one of the metadata_columns, we can use price filter while searching" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "\n", + "docs = await custom_store.asimilarity_search(query, filter=\"price_usd > 100\")\n", + "\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents with json filter\n", + "Since category is added in json metadata, we can use filter on JSON fields while searching\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "docs = await custom_store.asimilarity_search(\n", + " query, filter=\"metadata->>'category' = 'Electronics'\"\n", + ")\n", + "\n", + "print(docs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Search for documents with dictionary filter\n", + "Use a dictionary filter to make your filter DB Agnostic.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "docs = await custom_store.asimilarity_search(query, filter={\"category\": \"Electronics\"})\n", + "\n", + "print(docs)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/vectorstore.ipynb b/examples/vectorstore.ipynb index 4bb720e3..09b77b9e 100644 --- a/examples/vectorstore.ipynb +++ b/examples/vectorstore.ipynb @@ -72,7 +72,7 @@ "from langchain_core.documents import Document\n", "\n", "# See docker command above to launch a postgres instance with pgvector enabled.\n", - "connection = \"postgresql+psycopg://langchain:langchain@localhost:6024/langchain\" \n", + "connection = \"postgresql+psycopg://langchain:langchain@localhost:6024/langchain\"\n", "collection_name = \"my_docs\"\n", "embeddings = CohereEmbeddings()\n", "\n", @@ -126,17 +126,47 @@ "outputs": [], "source": [ "docs = [\n", - " Document(page_content='there are cats in the pond', metadata={\"id\": 1, \"location\": \"pond\", \"topic\": \"animals\"}),\n", - " Document(page_content='ducks are also found in the pond', metadata={\"id\": 2, \"location\": \"pond\", \"topic\": \"animals\"}),\n", - " Document(page_content='fresh apples are available at the market', metadata={\"id\": 3, \"location\": \"market\", \"topic\": \"food\"}),\n", - " Document(page_content='the market also sells fresh oranges', metadata={\"id\": 4, \"location\": \"market\", \"topic\": \"food\"}),\n", - " Document(page_content='the new art exhibit is fascinating', metadata={\"id\": 5, \"location\": \"museum\", \"topic\": \"art\"}),\n", - " Document(page_content='a sculpture exhibit is also at the museum', metadata={\"id\": 6, \"location\": \"museum\", \"topic\": \"art\"}),\n", - " Document(page_content='a new coffee shop opened on Main Street', metadata={\"id\": 7, \"location\": \"Main Street\", \"topic\": \"food\"}),\n", - " Document(page_content='the book club meets at the library', metadata={\"id\": 8, \"location\": \"library\", \"topic\": \"reading\"}),\n", - " Document(page_content='the library hosts a weekly story time for kids', metadata={\"id\": 9, \"location\": \"library\", \"topic\": \"reading\"}),\n", - " Document(page_content='a cooking class for beginners is offered at the community center', metadata={\"id\": 10, \"location\": \"community center\", \"topic\": \"classes\"})\n", - "]\n" + " Document(\n", + " page_content=\"there are cats in the pond\",\n", + " metadata={\"id\": 1, \"location\": \"pond\", \"topic\": \"animals\"},\n", + " ),\n", + " Document(\n", + " page_content=\"ducks are also found in the pond\",\n", + " metadata={\"id\": 2, \"location\": \"pond\", \"topic\": \"animals\"},\n", + " ),\n", + " Document(\n", + " page_content=\"fresh apples are available at the market\",\n", + " metadata={\"id\": 3, \"location\": \"market\", \"topic\": \"food\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the market also sells fresh oranges\",\n", + " metadata={\"id\": 4, \"location\": \"market\", \"topic\": \"food\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the new art exhibit is fascinating\",\n", + " metadata={\"id\": 5, \"location\": \"museum\", \"topic\": \"art\"},\n", + " ),\n", + " Document(\n", + " page_content=\"a sculpture exhibit is also at the museum\",\n", + " metadata={\"id\": 6, \"location\": \"museum\", \"topic\": \"art\"},\n", + " ),\n", + " Document(\n", + " page_content=\"a new coffee shop opened on Main Street\",\n", + " metadata={\"id\": 7, \"location\": \"Main Street\", \"topic\": \"food\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the book club meets at the library\",\n", + " metadata={\"id\": 8, \"location\": \"library\", \"topic\": \"reading\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the library hosts a weekly story time for kids\",\n", + " metadata={\"id\": 9, \"location\": \"library\", \"topic\": \"reading\"},\n", + " ),\n", + " Document(\n", + " page_content=\"a cooking class for beginners is offered at the community center\",\n", + " metadata={\"id\": 10, \"location\": \"community center\", \"topic\": \"classes\"},\n", + " ),\n", + "]" ] }, { @@ -159,7 +189,7 @@ } ], "source": [ - "vectorstore.add_documents(docs, ids=[doc.metadata['id'] for doc in docs])" + "vectorstore.add_documents(docs, ids=[doc.metadata[\"id\"] for doc in docs])" ] }, { @@ -191,7 +221,7 @@ } ], "source": [ - "vectorstore.similarity_search('kitty', k=10)" + "vectorstore.similarity_search(\"kitty\", k=10)" ] }, { @@ -212,17 +242,47 @@ "outputs": [], "source": [ "docs = [\n", - " Document(page_content='there are cats in the pond', metadata={\"id\": 1, \"location\": \"pond\", \"topic\": \"animals\"}),\n", - " Document(page_content='ducks are also found in the pond', metadata={\"id\": 2, \"location\": \"pond\", \"topic\": \"animals\"}),\n", - " Document(page_content='fresh apples are available at the market', metadata={\"id\": 3, \"location\": \"market\", \"topic\": \"food\"}),\n", - " Document(page_content='the market also sells fresh oranges', metadata={\"id\": 4, \"location\": \"market\", \"topic\": \"food\"}),\n", - " Document(page_content='the new art exhibit is fascinating', metadata={\"id\": 5, \"location\": \"museum\", \"topic\": \"art\"}),\n", - " Document(page_content='a sculpture exhibit is also at the museum', metadata={\"id\": 6, \"location\": \"museum\", \"topic\": \"art\"}),\n", - " Document(page_content='a new coffee shop opened on Main Street', metadata={\"id\": 7, \"location\": \"Main Street\", \"topic\": \"food\"}),\n", - " Document(page_content='the book club meets at the library', metadata={\"id\": 8, \"location\": \"library\", \"topic\": \"reading\"}),\n", - " Document(page_content='the library hosts a weekly story time for kids', metadata={\"id\": 9, \"location\": \"library\", \"topic\": \"reading\"}),\n", - " Document(page_content='a cooking class for beginners is offered at the community center', metadata={\"id\": 10, \"location\": \"community center\", \"topic\": \"classes\"})\n", - "]\n" + " Document(\n", + " page_content=\"there are cats in the pond\",\n", + " metadata={\"id\": 1, \"location\": \"pond\", \"topic\": \"animals\"},\n", + " ),\n", + " Document(\n", + " page_content=\"ducks are also found in the pond\",\n", + " metadata={\"id\": 2, \"location\": \"pond\", \"topic\": \"animals\"},\n", + " ),\n", + " Document(\n", + " page_content=\"fresh apples are available at the market\",\n", + " metadata={\"id\": 3, \"location\": \"market\", \"topic\": \"food\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the market also sells fresh oranges\",\n", + " metadata={\"id\": 4, \"location\": \"market\", \"topic\": \"food\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the new art exhibit is fascinating\",\n", + " metadata={\"id\": 5, \"location\": \"museum\", \"topic\": \"art\"},\n", + " ),\n", + " Document(\n", + " page_content=\"a sculpture exhibit is also at the museum\",\n", + " metadata={\"id\": 6, \"location\": \"museum\", \"topic\": \"art\"},\n", + " ),\n", + " Document(\n", + " page_content=\"a new coffee shop opened on Main Street\",\n", + " metadata={\"id\": 7, \"location\": \"Main Street\", \"topic\": \"food\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the book club meets at the library\",\n", + " metadata={\"id\": 8, \"location\": \"library\", \"topic\": \"reading\"},\n", + " ),\n", + " Document(\n", + " page_content=\"the library hosts a weekly story time for kids\",\n", + " metadata={\"id\": 9, \"location\": \"library\", \"topic\": \"reading\"},\n", + " ),\n", + " Document(\n", + " page_content=\"a cooking class for beginners is offered at the community center\",\n", + " metadata={\"id\": 10, \"location\": \"community center\", \"topic\": \"classes\"},\n", + " ),\n", + "]" ] }, { @@ -275,9 +335,7 @@ } ], "source": [ - "vectorstore.similarity_search('kitty', k=10, filter={\n", - " 'id': {'$in': [1, 5, 2, 9]}\n", - "})" + "vectorstore.similarity_search(\"kitty\", k=10, filter={\"id\": {\"$in\": [1, 5, 2, 9]}})" ] }, { @@ -309,10 +367,11 @@ } ], "source": [ - "vectorstore.similarity_search('ducks', k=10, filter={\n", - " 'id': {'$in': [1, 5, 2, 9]},\n", - " 'location': {'$in': [\"pond\", \"market\"]}\n", - "})" + "vectorstore.similarity_search(\n", + " \"ducks\",\n", + " k=10,\n", + " filter={\"id\": {\"$in\": [1, 5, 2, 9]}, \"location\": {\"$in\": [\"pond\", \"market\"]}},\n", + ")" ] }, { @@ -336,12 +395,15 @@ } ], "source": [ - "vectorstore.similarity_search('ducks', k=10, filter={\n", - " '$and': [\n", - " {'id': {'$in': [1, 5, 2, 9]}},\n", - " {'location': {'$in': [\"pond\", \"market\"]}},\n", - " ]\n", - "}\n", + "vectorstore.similarity_search(\n", + " \"ducks\",\n", + " k=10,\n", + " filter={\n", + " \"$and\": [\n", + " {\"id\": {\"$in\": [1, 5, 2, 9]}},\n", + " {\"location\": {\"$in\": [\"pond\", \"market\"]}},\n", + " ]\n", + " },\n", ")" ] }, @@ -372,9 +434,7 @@ } ], "source": [ - "vectorstore.similarity_search('bird', k=10, filter={\n", - " 'location': { \"$ne\": 'pond'}\n", - "})" + "vectorstore.similarity_search(\"bird\", k=10, filter={\"location\": {\"$ne\": \"pond\"}})" ] } ], diff --git a/langchain_postgres/__init__.py b/langchain_postgres/__init__.py index 241c8b89..14210ef1 100644 --- a/langchain_postgres/__init__.py +++ b/langchain_postgres/__init__.py @@ -1,7 +1,9 @@ from importlib import metadata from langchain_postgres.chat_message_histories import PostgresChatMessageHistory +from langchain_postgres.v2.engine import Column, PGEngine, ColumnDict from langchain_postgres.translator import PGVectorTranslator +from langchain_postgres.v2.vectorstores import PGVectorStore from langchain_postgres.vectorstores import PGVector try: @@ -12,7 +14,11 @@ __all__ = [ "__version__", + "Column", + "ColumnDict", + "PGEngine", "PostgresChatMessageHistory", "PGVector", + "PGVectorStore", "PGVectorTranslator", ] diff --git a/langchain_postgres/utils/pgvector_migrator.py b/langchain_postgres/utils/pgvector_migrator.py new file mode 100644 index 00000000..f338ca26 --- /dev/null +++ b/langchain_postgres/utils/pgvector_migrator.py @@ -0,0 +1,321 @@ +import asyncio +import json +import warnings +from typing import Any, AsyncIterator, Iterator, Optional, Sequence, TypeVar + +from sqlalchemy import RowMapping, text +from sqlalchemy.exc import ProgrammingError, SQLAlchemyError + +from ..v2.engine import PGEngine +from ..v2.vectorstores import PGVectorStore + +COLLECTIONS_TABLE = "langchain_pg_collection" +EMBEDDINGS_TABLE = "langchain_pg_embedding" + +T = TypeVar("T") + + +async def __aget_collection_uuid( + engine: PGEngine, + collection_name: str, +) -> str: + """ + Get the collection uuid for a collection present in PGVector tables. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The name of the collection to get the uuid for. + Returns: + The uuid corresponding to the collection. + """ + query = f"SELECT name, uuid FROM {COLLECTIONS_TABLE} WHERE name = :collection_name" + async with engine._pool.connect() as conn: + result = await conn.execute( + text(query), parameters={"collection_name": collection_name} + ) + result_map = result.mappings() + result_fetch = result_map.fetchone() + if result_fetch is None: + raise ValueError(f"Collection, {collection_name} not found.") + return result_fetch.uuid + + +async def __aextract_pgvector_collection( + engine: PGEngine, + collection_name: str, + batch_size: int = 1000, +) -> AsyncIterator[Sequence[RowMapping]]: + """ + Extract all data belonging to a PGVector collection. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The name of the collection to get the data for. + batch_size (int): The batch size for collection extraction. + Default: 1000. Optional. + + Yields: + The data present in the collection. + """ + try: + uuid_task = asyncio.create_task(__aget_collection_uuid(engine, collection_name)) + query = f"SELECT * FROM {EMBEDDINGS_TABLE} WHERE collection_id = :id" + async with engine._pool.connect() as conn: + uuid = await uuid_task + result_proxy = await conn.execute(text(query), parameters={"id": uuid}) + while True: + rows = result_proxy.fetchmany(size=batch_size) + if not rows: + break + yield [row._mapping for row in rows] + except ValueError as e: + raise ValueError(f"Collection, {collection_name} does not exist.") + except SQLAlchemyError as e: + raise ProgrammingError( + statement=f"Failed to extract data from collection '{collection_name}': {e}", + params={"id": uuid}, + orig=e, + ) from e + + +async def __concurrent_batch_insert( + data_batches: AsyncIterator[Sequence[RowMapping]], + vector_store: PGVectorStore, + max_concurrency: int = 100, +) -> None: + pending: set[Any] = set() + async for batch_data in data_batches: + pending.add( + asyncio.ensure_future( + vector_store.aadd_embeddings( + texts=[data.document for data in batch_data], + embeddings=[json.loads(data.embedding) for data in batch_data], + metadatas=[data.cmetadata for data in batch_data], + ids=[data.id for data in batch_data], + ) + ) + ) + if len(pending) >= max_concurrency: + _, pending = await asyncio.wait( + pending, return_when=asyncio.FIRST_COMPLETED + ) + if pending: + await asyncio.wait(pending) + + +async def __amigrate_pgvector_collection( + engine: PGEngine, + collection_name: str, + vector_store: PGVectorStore, + delete_pg_collection: Optional[bool] = False, + insert_batch_size: int = 1000, +) -> None: + """ + Migrate all data present in a PGVector collection to use separate tables for each collection. + The new data format is compatible with the PGVectoreStore interface. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The collection to migrate. + vector_store (PGVectorStore): The PGVectorStore object corresponding to the new collection table. + delete_pg_collection (bool): An option to delete the original data upon migration. + Default: False. Optional. + insert_batch_size (int): Number of rows to insert at once in the table. + Default: 1000. + """ + destination_table = vector_store.get_table_name() + + # Get row count in PGVector collection + uuid_task = asyncio.create_task(__aget_collection_uuid(engine, collection_name)) + query = ( + f"SELECT COUNT(*) FROM {EMBEDDINGS_TABLE} WHERE collection_id=:collection_id" + ) + async with engine._pool.connect() as conn: + uuid = await uuid_task + result = await conn.execute(text(query), parameters={"collection_id": uuid}) + result_map = result.mappings() + collection_data_len = result_map.fetchone() + if collection_data_len is None: + warnings.warn(f"Collection, {collection_name} contains no elements.") + return + + # Extract data from the collection and batch insert into the new table + data_batches = __aextract_pgvector_collection( + engine, collection_name, batch_size=insert_batch_size + ) + await __concurrent_batch_insert(data_batches, vector_store, max_concurrency=100) + + # Validate data migration + query = f"SELECT COUNT(*) FROM {destination_table}" + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + table_size = result_map.fetchone() + if not table_size: + raise ValueError(f"Table: {destination_table} does not exist.") + + if collection_data_len["count"] != table_size["count"]: + raise ValueError( + "All data not yet migrated.\n" + f"Original row count: {collection_data_len['count']}\n" + f"Collection table, {destination_table} row count: {table_size['count']}" + ) + elif delete_pg_collection: + # Delete PGVector data + query = f"DELETE FROM {EMBEDDINGS_TABLE} WHERE collection_id=:collection_id" + async with engine._pool.connect() as conn: + await conn.execute(text(query), parameters={"collection_id": uuid}) + await conn.commit() + + query = f"DELETE FROM {COLLECTIONS_TABLE} WHERE name=:collection_name" + async with engine._pool.connect() as conn: + await conn.execute( + text(query), parameters={"collection_name": collection_name} + ) + await conn.commit() + print(f"Successfully deleted PGVector collection, {collection_name}") + + +async def __alist_pgvector_collection_names( + engine: PGEngine, +) -> list[str]: + """Lists all collection names present in PGVector table.""" + try: + query = f"SELECT name from {COLLECTIONS_TABLE}" + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + all_rows = result_map.fetchall() + return [row["name"] for row in all_rows] + except ProgrammingError as e: + raise ValueError( + "Please provide the correct collection table name: " + str(e) + ) from e + + +async def aextract_pgvector_collection( + engine: PGEngine, + collection_name: str, + batch_size: int = 1000, +) -> AsyncIterator[Sequence[RowMapping]]: + """ + Extract all data belonging to a PGVector collection. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The name of the collection to get the data for. + batch_size (int): The batch size for collection extraction. + Default: 1000. Optional. + + Yields: + The data present in the collection. + """ + iterator = __aextract_pgvector_collection(engine, collection_name, batch_size) + while True: + try: + result = await engine._run_as_async(iterator.__anext__()) + yield result + except StopAsyncIteration: + break + + +async def alist_pgvector_collection_names( + engine: PGEngine, +) -> list[str]: + """Lists all collection names present in PGVector table.""" + return await engine._run_as_async(__alist_pgvector_collection_names(engine)) + + +async def amigrate_pgvector_collection( + engine: PGEngine, + collection_name: str, + vector_store: PGVectorStore, + delete_pg_collection: Optional[bool] = False, + insert_batch_size: int = 1000, +) -> None: + """ + Migrate all data present in a PGVector collection to use separate tables for each collection. + The new data format is compatible with the PGVectorStore interface. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The collection to migrate. + vector_store (PGVectorStore): The PGVectorStore object corresponding to the new collection table. + use_json_metadata (bool): An option to keep the PGVector metadata as json in the new table. + Default: False. Optional. + delete_pg_collection (bool): An option to delete the original data upon migration. + Default: False. Optional. + insert_batch_size (int): Number of rows to insert at once in the table. + Default: 1000. + """ + await engine._run_as_async( + __amigrate_pgvector_collection( + engine, + collection_name, + vector_store, + delete_pg_collection, + insert_batch_size, + ) + ) + + +def extract_pgvector_collection( + engine: PGEngine, + collection_name: str, + batch_size: int = 1000, +) -> Iterator[Sequence[RowMapping]]: + """ + Extract all data belonging to a PGVector collection. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The name of the collection to get the data for. + batch_size (int): The batch size for collection extraction. + Default: 1000. Optional. + + Yields: + The data present in the collection. + """ + iterator = __aextract_pgvector_collection(engine, collection_name, batch_size) + while True: + try: + result = engine._run_as_sync(iterator.__anext__()) + yield result + except StopAsyncIteration: + break + + +def list_pgvector_collection_names(engine: PGEngine) -> list[str]: + """Lists all collection names present in PGVector table.""" + return engine._run_as_sync(__alist_pgvector_collection_names(engine)) + + +def migrate_pgvector_collection( + engine: PGEngine, + collection_name: str, + vector_store: PGVectorStore, + delete_pg_collection: Optional[bool] = False, + insert_batch_size: int = 1000, +) -> None: + """ + Migrate all data present in a PGVector collection to use separate tables for each collection. + The new data format is compatible with the PGVectorStore interface. + + Args: + engine (PGEngine): The PG engine corresponding to the Database. + collection_name (str): The collection to migrate. + vector_store (PGVectorStore): The PGVectorStore object corresponding to the new collection table. + delete_pg_collection (bool): An option to delete the original data upon migration. + Default: False. Optional. + insert_batch_size (int): Number of rows to insert at once in the table. + Default: 1000. + """ + engine._run_as_sync( + __amigrate_pgvector_collection( + engine, + collection_name, + vector_store, + delete_pg_collection, + insert_batch_size, + ) + ) diff --git a/langchain_postgres/v2/__init__.py b/langchain_postgres/v2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/langchain_postgres/v2/async_vectorstore.py b/langchain_postgres/v2/async_vectorstore.py new file mode 100644 index 00000000..bae31ba2 --- /dev/null +++ b/langchain_postgres/v2/async_vectorstore.py @@ -0,0 +1,1248 @@ +# TODO: Remove below import when minimum supported Python version is 3.10 +from __future__ import annotations + +import copy +import json +import re +import uuid +from typing import Any, Callable, Iterable, Optional, Sequence + +import numpy as np +from langchain_core.documents import Document +from langchain_core.embeddings import Embeddings +from langchain_core.vectorstores import VectorStore, utils +from sqlalchemy import RowMapping, text +from sqlalchemy.ext.asyncio import AsyncEngine + +from .engine import PGEngine +from .indexes import ( + DEFAULT_DISTANCE_STRATEGY, + DEFAULT_INDEX_NAME_SUFFIX, + BaseIndex, + DistanceStrategy, + ExactNearestNeighbor, + QueryOptions, +) + +COMPARISONS_TO_NATIVE = { + "$eq": "=", + "$ne": "!=", + "$lt": "<", + "$lte": "<=", + "$gt": ">", + "$gte": ">=", +} + +SPECIAL_CASED_OPERATORS = { + "$in", + "$nin", + "$between", + "$exists", +} + +TEXT_OPERATORS = { + "$like", + "$ilike", +} + +LOGICAL_OPERATORS = {"$and", "$or", "$not"} + +SUPPORTED_OPERATORS = ( + set(COMPARISONS_TO_NATIVE) + .union(TEXT_OPERATORS) + .union(LOGICAL_OPERATORS) + .union(SPECIAL_CASED_OPERATORS) +) + + +class AsyncPGVectorStore(VectorStore): + """Postgres Vector Store class""" + + __create_key = object() + + def __init__( + self, + key: object, + engine: AsyncEngine, + embedding_service: Embeddings, + table_name: str, + *, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: Optional[str] = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + ): + """AsyncPGVectorStore constructor. + Args: + key (object): Prevent direct constructor usage. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + embedding_service (Embeddings): Text embedding model to use. + table_name (str): Name of the existing table or the table to be created. + schema_name (str, optional): Name of the database schema. Defaults to "public". + content_column (str): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str]): Column(s) that represent a document's metadata. + id_column (str): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + + Raises: + Exception: If called directly by user. + """ + if key != AsyncPGVectorStore.__create_key: + raise Exception( + "Only create class through 'create' or 'create_sync' methods!" + ) + + self.engine = engine + self.embedding_service = embedding_service + self.table_name = table_name + self.schema_name = schema_name + self.content_column = content_column + self.embedding_column = embedding_column + self.metadata_columns = metadata_columns if metadata_columns is not None else [] + self.id_column = id_column + self.metadata_json_column = metadata_json_column + self.distance_strategy = distance_strategy + self.k = k + self.fetch_k = fetch_k + self.lambda_mult = lambda_mult + self.index_query_options = index_query_options + + @classmethod + async def create( + cls: type[AsyncPGVectorStore], + engine: PGEngine, + embedding_service: Embeddings, + table_name: str, + *, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: Optional[str] = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + ) -> AsyncPGVectorStore: + """Create an AsyncPGVectorStore instance. + + Args: + engine (PGEngine): Connection pool engine for managing connections to postgres database. + embedding_service (Embeddings): Text embedding model to use. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + content_column (str): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str]): Column(s) that represent a document's metadata. + ignore_metadata_columns (list[str]): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Returns: + AsyncPGVectorStore + """ + + if metadata_columns is None: + metadata_columns = [] + + if metadata_columns and ignore_metadata_columns: + raise ValueError( + "Can not use both metadata_columns and ignore_metadata_columns." + ) + # Get field type information + stmt = "SELECT column_name, data_type FROM information_schema.columns WHERE table_name = :table_name AND table_schema = :schema_name" + async with engine._pool.connect() as conn: + result = await conn.execute( + text(stmt), {"table_name": table_name, "schema_name": schema_name} + ) + result_map = result.mappings() + results = result_map.fetchall() + columns = {} + for field in results: + columns[field["column_name"]] = field["data_type"] + + # Check columns + if id_column not in columns: + raise ValueError(f"Id column, {id_column}, does not exist.") + if content_column not in columns: + raise ValueError(f"Content column, {content_column}, does not exist.") + content_type = columns[content_column] + if content_type != "text" and "char" not in content_type: + raise ValueError( + f"Content column, {content_column}, is type, {content_type}. It must be a type of character string." + ) + if embedding_column not in columns: + raise ValueError(f"Embedding column, {embedding_column}, does not exist.") + if columns[embedding_column] != "USER-DEFINED": + raise ValueError( + f"Embedding column, {embedding_column}, is not type Vector." + ) + + metadata_json_column = ( + None if metadata_json_column not in columns else metadata_json_column + ) + + # If using metadata_columns check to make sure column exists + for column in metadata_columns: + if column not in columns: + raise ValueError(f"Metadata column, {column}, does not exist.") + + # If using ignore_metadata_columns, filter out known columns and set known metadata columns + all_columns = columns + if ignore_metadata_columns: + for column in ignore_metadata_columns: + del all_columns[column] + + del all_columns[id_column] + del all_columns[content_column] + del all_columns[embedding_column] + metadata_columns = [k for k in all_columns.keys()] + + return cls( + cls.__create_key, + engine._pool, + embedding_service, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + id_column=id_column, + metadata_json_column=metadata_json_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + + @property + def embeddings(self) -> Embeddings: + return self.embedding_service + + async def aadd_embeddings( + self, + texts: Iterable[str], + embeddings: list[list[float]], + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Add data along with embeddings to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + if not ids: + ids = [str(uuid.uuid4()) for _ in texts] + else: + # This is done to fill in any missing ids + ids = [id if id is not None else str(uuid.uuid4()) for id in ids] + if not metadatas: + metadatas = [{} for _ in texts] + + # Check for inline embedding capability + inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None) + can_inline_embed = callable(inline_embed_func) + # Insert embeddings + for id, content, embedding, metadata in zip(ids, texts, embeddings, metadatas): + metadata_col_names = ( + ", " + ", ".join(f'"{col}"' for col in self.metadata_columns) + if len(self.metadata_columns) > 0 + else "" + ) + insert_stmt = f'INSERT INTO "{self.schema_name}"."{self.table_name}"("{self.id_column}", "{self.content_column}", "{self.embedding_column}"{metadata_col_names}' + values = { + "id": id, + "content": content, + "embedding": str([float(dimension) for dimension in embedding]), + } + values_stmt = "VALUES (:id, :content, :embedding" + + if not embedding and can_inline_embed: + values_stmt = f"VALUES (:id, :content, {self.embedding_service.embed_query_inline(content)}" # type: ignore + + # Add metadata + extra = copy.deepcopy(metadata) + for metadata_column in self.metadata_columns: + if metadata_column in metadata: + values_stmt += f", :{metadata_column}" + values[metadata_column] = metadata[metadata_column] + del extra[metadata_column] + else: + values_stmt += ",null" + + # Add JSON column and/or close statement + insert_stmt += ( + f""", "{self.metadata_json_column}")""" + if self.metadata_json_column + else ")" + ) + if self.metadata_json_column: + values_stmt += ", :extra)" + values["extra"] = json.dumps(extra) + else: + values_stmt += ")" + + upsert_stmt = f' ON CONFLICT ("{self.id_column}") DO UPDATE SET "{self.content_column}" = EXCLUDED."{self.content_column}", "{self.embedding_column}" = EXCLUDED."{self.embedding_column}"' + + if self.metadata_json_column: + upsert_stmt += f', "{self.metadata_json_column}" = EXCLUDED."{self.metadata_json_column}"' + + for column in self.metadata_columns: + upsert_stmt += f', "{column}" = EXCLUDED."{column}"' + + upsert_stmt += ";" + + query = insert_stmt + values_stmt + upsert_stmt + async with self.engine.connect() as conn: + await conn.execute(text(query), values) + await conn.commit() + + return ids + + async def aadd_texts( + self, + texts: Iterable[str], + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Embed texts and add to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + # Check for inline embedding query + inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None) + if callable(inline_embed_func): + embeddings: list[list[float]] = [[] for _ in list(texts)] + else: + embeddings = await self.embedding_service.aembed_documents(list(texts)) + + ids = await self.aadd_embeddings( + texts, embeddings, metadatas=metadatas, ids=ids, **kwargs + ) + return ids + + async def aadd_documents( + self, + documents: list[Document], + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Embed documents and add to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + texts = [doc.page_content for doc in documents] + metadatas = [doc.metadata for doc in documents] + if not ids: + ids = [doc.id for doc in documents] + ids = await self.aadd_texts(texts, metadatas=metadatas, ids=ids, **kwargs) + return ids + + async def adelete( + self, + ids: Optional[list] = None, + **kwargs: Any, + ) -> Optional[bool]: + """Delete records from the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + if not ids: + return False + + placeholders = ", ".join(f":id_{i}" for i in range(len(ids))) + param_dict = {f"id_{i}": id for i, id in enumerate(ids)} + query = f'DELETE FROM "{self.schema_name}"."{self.table_name}" WHERE {self.id_column} in ({placeholders})' + async with self.engine.connect() as conn: + await conn.execute(text(query), param_dict) + await conn.commit() + return True + + @classmethod + async def afrom_texts( # type: ignore[override] + cls: type[AsyncPGVectorStore], + texts: list[str], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + *, + schema_name: str = "public", + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + **kwargs: Any, + ) -> AsyncPGVectorStore: + """Create an AsyncPGVectorStore instance from texts. + + Args: + texts (list[str]): Texts to add to the vector store. + embedding (Embeddings): Text embedding model to use. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + table_name (str): Name of an existing table. + metadatas (Optional[list[dict]]): List of metadatas to add to table records. + ids: (Optional[list[str]]): List of IDs to add to table records. + content_column (str): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str]): Column(s) that represent a document's metadata. + ignore_metadata_columns (list[str]): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + + Returns: + AsyncPGVectorStore + """ + vs = await cls.create( + engine, + embedding, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + id_column=id_column, + metadata_json_column=metadata_json_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + await vs.aadd_texts(texts, metadatas=metadatas, ids=ids, **kwargs) + return vs + + @classmethod + async def afrom_documents( # type: ignore[override] + cls: type[AsyncPGVectorStore], + documents: list[Document], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + *, + schema_name: str = "public", + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + **kwargs: Any, + ) -> AsyncPGVectorStore: + """Create an AsyncPGVectorStore instance from documents. + + Args: + documents (list[Document]): Documents to add to the vector store. + embedding (Embeddings): Text embedding model to use. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + table_name (str): Name of an existing table. + metadatas (Optional[list[dict]]): List of metadatas to add to table records. + ids: (Optional[list[str]]): List of IDs to add to table records. + content_column (str): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str]): Column(s) that represent a document's metadata. + ignore_metadata_columns (list[str]): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + + Returns: + AsyncPGVectorStore + """ + + vs = await cls.create( + engine, + embedding, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + id_column=id_column, + metadata_json_column=metadata_json_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + texts = [doc.page_content for doc in documents] + metadatas = [doc.metadata for doc in documents] + await vs.aadd_texts(texts, metadatas=metadatas, ids=ids, **kwargs) + return vs + + async def __query_collection( + self, + embedding: list[float], + *, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> Sequence[RowMapping]: + """Perform similarity search query on database.""" + k = k if k else self.k + operator = self.distance_strategy.operator + search_function = self.distance_strategy.search_function + + columns = self.metadata_columns + [ + self.id_column, + self.content_column, + self.embedding_column, + ] + if self.metadata_json_column: + columns.append(self.metadata_json_column) + + column_names = ", ".join(f'"{col}"' for col in columns) + + if filter and isinstance(filter, dict): + filter = self._create_filter_clause(filter) + filter = f"WHERE {filter}" if filter else "" + inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None) + if not embedding and callable(inline_embed_func) and "query" in kwargs: + query_embedding = self.embedding_service.embed_query_inline(kwargs["query"]) # type: ignore + else: + query_embedding = f"{[float(dimension) for dimension in embedding]}" + stmt = f'SELECT {column_names}, {search_function}("{self.embedding_column}", :query_embedding) as distance FROM "{self.schema_name}"."{self.table_name}" {filter} ORDER BY "{self.embedding_column}" {operator} :query_embedding LIMIT :k;' + param_dict = {"query_embedding": query_embedding, "k": k} + if self.index_query_options: + async with self.engine.connect() as conn: + # Set each query option individually + for query_option in self.index_query_options.to_parameter(): + query_options_stmt = f"SET LOCAL {query_option};" + await conn.execute(text(query_options_stmt)) + result = await conn.execute(text(stmt), param_dict) + result_map = result.mappings() + results = result_map.fetchall() + else: + async with self.engine.connect() as conn: + result = await conn.execute(text(stmt), param_dict) + result_map = result.mappings() + results = result_map.fetchall() + return results + + async def asimilarity_search( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected by similarity search on query.""" + inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None) + embedding = ( + [] + if callable(inline_embed_func) + else await self.embedding_service.aembed_query(text=query) + ) + kwargs["query"] = query + + return await self.asimilarity_search_by_vector( + embedding=embedding, k=k, filter=filter, **kwargs + ) + + def _select_relevance_score_fn(self) -> Callable[[float], float]: + """Select a relevance function based on distance strategy.""" + # Calculate distance strategy provided in + # vectorstore constructor + if self.distance_strategy == DistanceStrategy.COSINE_DISTANCE: + return self._cosine_relevance_score_fn + if self.distance_strategy == DistanceStrategy.INNER_PRODUCT: + return self._max_inner_product_relevance_score_fn + elif self.distance_strategy == DistanceStrategy.EUCLIDEAN: + return self._euclidean_relevance_score_fn + + async def asimilarity_search_with_score( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected by similarity search on query.""" + inline_embed_func = getattr(self.embedding_service, "embed_query_inline", None) + embedding = ( + [] + if callable(inline_embed_func) + else await self.embedding_service.aembed_query(text=query) + ) + kwargs["query"] = query + + docs = await self.asimilarity_search_with_score_by_vector( + embedding=embedding, k=k, filter=filter, **kwargs + ) + return docs + + async def asimilarity_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected by vector similarity search.""" + docs_and_scores = await self.asimilarity_search_with_score_by_vector( + embedding=embedding, k=k, filter=filter, **kwargs + ) + + return [doc for doc, _ in docs_and_scores] + + async def asimilarity_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected by vector similarity search.""" + results = await self.__query_collection( + embedding=embedding, k=k, filter=filter, **kwargs + ) + + documents_with_scores = [] + for row in results: + metadata = ( + row[self.metadata_json_column] + if self.metadata_json_column and row[self.metadata_json_column] + else {} + ) + for col in self.metadata_columns: + metadata[col] = row[col] + documents_with_scores.append( + ( + Document( + page_content=row[self.content_column], + metadata=metadata, + id=str(row[self.id_column]), + ), + row["distance"], + ) + ) + + return documents_with_scores + + async def amax_marginal_relevance_search( + self, + query: str, + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected using the maximal marginal relevance.""" + embedding = await self.embedding_service.aembed_query(text=query) + + return await self.amax_marginal_relevance_search_by_vector( + embedding=embedding, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + filter=filter, + **kwargs, + ) + + async def amax_marginal_relevance_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected using the 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, + **kwargs, + ) + ) + + return [result[0] for result in docs_and_scores] + + async def amax_marginal_relevance_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected using the maximal marginal relevance.""" + results = await self.__query_collection( + embedding=embedding, k=fetch_k, filter=filter, **kwargs + ) + + k = k if k else self.k + fetch_k = fetch_k if fetch_k else self.fetch_k + lambda_mult = lambda_mult if lambda_mult else self.lambda_mult + embedding_list = [json.loads(row[self.embedding_column]) for row in results] + mmr_selected = utils.maximal_marginal_relevance( + np.array(embedding, dtype=np.float32), + embedding_list, + k=k, + lambda_mult=lambda_mult, + ) + + documents_with_scores = [] + for row in results: + metadata = ( + row[self.metadata_json_column] + if self.metadata_json_column and row[self.metadata_json_column] + else {} + ) + for col in self.metadata_columns: + metadata[col] = row[col] + documents_with_scores.append( + ( + Document( + page_content=row[self.content_column], + metadata=metadata, + id=str(row[self.id_column]), + ), + row["distance"], + ) + ) + + return [r for i, r in enumerate(documents_with_scores) if i in mmr_selected] + + async def aapply_vector_index( + self, + index: BaseIndex, + name: Optional[str] = None, + *, + concurrently: bool = False, + ) -> None: + """Create index in the vector store table.""" + if isinstance(index, ExactNearestNeighbor): + await self.adrop_vector_index() + return + + # if extension name is mentioned, create the extension + if index.extension_name: + async with self.engine.connect() as conn: + await conn.execute( + text(f"CREATE EXTENSION IF NOT EXISTS {index.extension_name}") + ) + await conn.commit() + function = index.get_index_function() + + filter = f"WHERE ({index.partial_indexes})" if index.partial_indexes else "" + params = "WITH " + index.index_options() + if name is None: + if index.name == None: + index.name = self.table_name + DEFAULT_INDEX_NAME_SUFFIX + name = index.name + stmt = f'CREATE INDEX {"CONCURRENTLY" if concurrently else ""} "{name}" ON "{self.schema_name}"."{self.table_name}" USING {index.index_type} ({self.embedding_column} {function}) {params} {filter};' + if concurrently: + async with self.engine.connect() as conn: + autocommit_conn = await conn.execution_options( + isolation_level="AUTOCOMMIT" + ) + await autocommit_conn.execute(text(stmt)) + else: + async with self.engine.connect() as conn: + await conn.execute(text(stmt)) + await conn.commit() + + async def areindex(self, index_name: Optional[str] = None) -> None: + """Re-index the vector store table.""" + index_name = index_name or self.table_name + DEFAULT_INDEX_NAME_SUFFIX + query = f'REINDEX INDEX "{index_name}";' + async with self.engine.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + async def adrop_vector_index( + self, + index_name: Optional[str] = None, + ) -> None: + """Drop the vector index.""" + index_name = index_name or self.table_name + DEFAULT_INDEX_NAME_SUFFIX + query = f'DROP INDEX IF EXISTS "{index_name}";' + async with self.engine.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + async def is_valid_index( + self, + index_name: Optional[str] = None, + ) -> bool: + """Check if index exists in the table.""" + index_name = index_name or self.table_name + DEFAULT_INDEX_NAME_SUFFIX + query = f""" + SELECT tablename, indexname + FROM pg_indexes + WHERE tablename = :table_name AND schemaname = :schema_name AND indexname = :index_name; + """ + param_dict = { + "table_name": self.table_name, + "schema_name": self.schema_name, + "index_name": index_name, + } + async with self.engine.connect() as conn: + result = await conn.execute(text(query), param_dict) + result_map = result.mappings() + results = result_map.fetchall() + return bool(len(results) == 1) + + async def aget_by_ids(self, ids: Sequence[str]) -> list[Document]: + """Get documents by ids.""" + + columns = self.metadata_columns + [ + self.id_column, + self.content_column, + ] + if self.metadata_json_column: + columns.append(self.metadata_json_column) + + column_names = ", ".join(f'"{col}"' for col in columns) + + placeholders = ", ".join(f":id_{i}" for i in range(len(ids))) + param_dict = {f"id_{i}": id for i, id in enumerate(ids)} + + query = f'SELECT {column_names} FROM "{self.schema_name}"."{self.table_name}" WHERE "{self.id_column}" IN ({placeholders});' + + async with self.engine.connect() as conn: + result = await conn.execute(text(query), param_dict) + result_map = result.mappings() + results = result_map.fetchall() + + documents = [] + for row in results: + metadata = ( + row[self.metadata_json_column] + if self.metadata_json_column and row[self.metadata_json_column] + else {} + ) + for col in self.metadata_columns: + metadata[col] = row[col] + documents.append( + ( + Document( + page_content=row[self.content_column], + metadata=metadata, + id=str(row[self.id_column]), + ) + ) + ) + + return documents + + def _handle_field_filter( + self, + *, + field: str, + value: Any, + ) -> str: + """Create a filter for a specific field. + + Args: + field: name of field + value: value to filter + If provided as is then this will be an equality filter + If provided as a dictionary then this will be a filter, the key + will be the operator and the value will be the value to filter by + + Returns: + sql where query as a string + """ + if not isinstance(field, str): + raise ValueError( + f"field should be a string but got: {type(field)} with value: {field}" + ) + + if field.startswith("$"): + raise ValueError( + f"Invalid filter condition. Expected a field but got an operator: " + f"{field}" + ) + + # Allow [a-zA-Z0-9_], disallow $ for now until we support escape characters + if not field.isidentifier(): + raise ValueError( + f"Invalid field name: {field}. Expected a valid identifier." + ) + + if isinstance(value, dict): + # This is a filter specification + if len(value) != 1: + raise ValueError( + "Invalid filter condition. Expected a value which " + "is a dictionary with a single key that corresponds to an operator " + f"but got a dictionary with {len(value)} keys. The first few " + f"keys are: {list(value.keys())[:3]}" + ) + operator, filter_value = list(value.items())[0] + # Verify that that operator is an operator + if operator not in SUPPORTED_OPERATORS: + raise ValueError( + f"Invalid operator: {operator}. " + f"Expected one of {SUPPORTED_OPERATORS}" + ) + else: # Then we assume an equality operator + operator = "$eq" + filter_value = value + + if operator in COMPARISONS_TO_NATIVE: + # Then we implement an equality filter + # native is trusted input + if isinstance(filter_value, str): + filter_value = f"'{filter_value}'" + native = COMPARISONS_TO_NATIVE[operator] + return f"({field} {native} {filter_value})" + elif operator == "$between": + # Use AND with two comparisons + low, high = filter_value + + return f"({field} BETWEEN {low} AND {high})" + elif operator in {"$in", "$nin", "$like", "$ilike"}: + # We'll do force coercion to text + if operator in {"$in", "$nin"}: + for val in filter_value: + if not isinstance(val, (str, int, float)): + raise NotImplementedError( + f"Unsupported type: {type(val)} for value: {val}" + ) + + if isinstance(val, bool): # b/c bool is an instance of int + raise NotImplementedError( + f"Unsupported type: {type(val)} for value: {val}" + ) + + if operator in {"$in"}: + values = str(tuple(val for val in filter_value)) + return f"({field} IN {values})" + elif operator in {"$nin"}: + values = str(tuple(val for val in filter_value)) + return f"({field} NOT IN {values})" + elif operator in {"$like"}: + return f"({field} LIKE '{filter_value}')" + elif operator in {"$ilike"}: + return f"({field} ILIKE '{filter_value}')" + else: + raise NotImplementedError() + elif operator == "$exists": + if not isinstance(filter_value, bool): + raise ValueError( + "Expected a boolean value for $exists " + f"operator, but got: {filter_value}" + ) + else: + if filter_value: + return f"({field} IS NOT NULL)" + else: + return f"({field} IS NULL)" + else: + raise NotImplementedError() + + def _create_filter_clause(self, filters: Any) -> str: + """Create LangChain filter representation to matching SQL where clauses + + Args: + filters: Dictionary of filters to apply to the query. + + Returns: + String containing the sql where query. + """ + + if not isinstance(filters, dict): + raise ValueError( + f"Invalid type: Expected a dictionary but got type: {type(filters)}" + ) + if len(filters) == 1: + # The only operators allowed at the top level are $AND, $OR, and $NOT + # First check if an operator or a field + key, value = list(filters.items())[0] + if key.startswith("$"): + # Then it's an operator + if key.lower() not in ["$and", "$or", "$not"]: + raise ValueError( + f"Invalid filter condition. Expected $and, $or or $not " + f"but got: {key}" + ) + else: + # Then it's a field + return self._handle_field_filter(field=key, value=filters[key]) + + if key.lower() == "$and" or key.lower() == "$or": + if not isinstance(value, list): + raise ValueError( + f"Expected a list, but got {type(value)} for value: {value}" + ) + op = key[1:].upper() # Extract the operator + filter_clause = [self._create_filter_clause(el) for el in value] + if len(filter_clause) > 1: + return f"({f' {op} '.join(filter_clause)})" + elif len(filter_clause) == 1: + return filter_clause[0] + else: + raise ValueError( + "Invalid filter condition. Expected a dictionary " + "but got an empty dictionary" + ) + elif key.lower() == "$not": + if isinstance(value, list): + not_conditions = [ + self._create_filter_clause(item) for item in value + ] + not_stmts = [f"NOT {condition}" for condition in not_conditions] + return f"({' AND '.join(not_stmts)})" + elif isinstance(value, dict): + not_ = self._create_filter_clause(value) + return f"(NOT {not_})" + else: + raise ValueError( + f"Invalid filter condition. Expected a dictionary " + f"or a list but got: {type(value)}" + ) + else: + raise ValueError( + f"Invalid filter condition. Expected $and, $or or $not " + f"but got: {key}" + ) + elif len(filters) > 1: + # Then all keys have to be fields (they cannot be operators) + for key in filters.keys(): + if key.startswith("$"): + raise ValueError( + f"Invalid filter condition. Expected a field but got: {key}" + ) + # These should all be fields and combined using an $and operator + and_ = [ + self._handle_field_filter(field=k, value=v) for k, v in filters.items() + ] + if len(and_) > 1: + return f"({' AND '.join(and_)})" + elif len(and_) == 1: + return and_[0] + else: + raise ValueError( + "Invalid filter condition. Expected a dictionary " + "but got an empty dictionary" + ) + else: + return "" + + def get_by_ids(self, ids: Sequence[str]) -> list[Document]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def add_texts( + self, + texts: Iterable[str], + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def add_documents( + self, + documents: list[Document], + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def delete( + self, + ids: Optional[list] = None, + **kwargs: Any, + ) -> Optional[bool]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + @classmethod + def from_texts( # type: ignore[override] + cls: type[AsyncPGVectorStore], + texts: list[str], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + **kwargs: Any, + ) -> AsyncPGVectorStore: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + @classmethod + def from_documents( # type: ignore[override] + cls: type[AsyncPGVectorStore], + documents: list[Document], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + **kwargs: Any, + ) -> AsyncPGVectorStore: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def similarity_search( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def similarity_search_with_score( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def similarity_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def similarity_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def max_marginal_relevance_search( + self, + query: str, + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def max_marginal_relevance_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) + + def max_marginal_relevance_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + raise NotImplementedError( + "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + ) diff --git a/langchain_postgres/v2/engine.py b/langchain_postgres/v2/engine.py new file mode 100644 index 00000000..0b87d1f9 --- /dev/null +++ b/langchain_postgres/v2/engine.py @@ -0,0 +1,351 @@ +from __future__ import annotations + +import asyncio +from dataclasses import dataclass +from threading import Thread +from typing import TYPE_CHECKING, Any, Awaitable, Optional, TypeVar, TypedDict, Union + +from sqlalchemy import text +from sqlalchemy.engine import URL +from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine + +if TYPE_CHECKING: + import asyncpg # type: ignore + +T = TypeVar("T") + + +class ColumnDict(TypedDict): + name: str + data_type: str + nullable: bool + + +@dataclass +class Column: + name: str + data_type: str + nullable: bool = True + + def __post_init__(self) -> None: + """Check if initialization parameters are valid. + + Raises: + ValueError: If Column name is not string. + ValueError: If data_type is not type string. + """ + + if not isinstance(self.name, str): + raise ValueError("Column name must be type string") + if not isinstance(self.data_type, str): + raise ValueError("Column data_type must be type string") + + +class PGEngine: + """A class for managing connections to a Postgres database.""" + + _default_loop: Optional[asyncio.AbstractEventLoop] = None + _default_thread: Optional[Thread] = None + __create_key = object() + + def __init__( + self, + key: object, + pool: AsyncEngine, + loop: Optional[asyncio.AbstractEventLoop], + thread: Optional[Thread], + ) -> None: + """PGEngine constructor. + + Args: + key (object): Prevent direct constructor usage. + pool (AsyncEngine): Async engine connection pool. + loop (Optional[asyncio.AbstractEventLoop]): Async event loop used to create the engine. + thread (Optional[Thread]): Thread used to create the engine async. + + Raises: + Exception: If the constructor is called directly by the user. + """ + + if key != PGEngine.__create_key: + raise Exception( + "Only create class through 'from_connection_string' or 'from_engine' methods!" + ) + self._pool = pool + self._loop = loop + self._thread = thread + + @classmethod + def from_engine( + cls: type[PGEngine], + engine: AsyncEngine, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> PGEngine: + """Create an PGEngine instance from an AsyncEngine.""" + return cls(cls.__create_key, engine, loop, None) + + @classmethod + def from_connection_string( + cls, + url: str | URL, + **kwargs: Any, + ) -> PGEngine: + """Create an PGEngine instance from arguments + + Args: + url (Optional[str]): the URL used to connect to a database. Use url or set other arguments. + + Raises: + ValueError: If not all database url arguments are specified + + Returns: + PGEngine + """ + # Running a loop in a background thread allows us to support + # async methods from non-async environments + if cls._default_loop is None: + cls._default_loop = asyncio.new_event_loop() + cls._default_thread = Thread( + target=cls._default_loop.run_forever, daemon=True + ) + cls._default_thread.start() + + engine = create_async_engine(url, **kwargs) + return cls(cls.__create_key, engine, cls._default_loop, cls._default_thread) + + async def _run_as_async(self, coro: Awaitable[T]) -> T: + """Run an async coroutine asynchronously""" + # If a loop has not been provided, attempt to run in current thread + if not self._loop: + return await coro + # Otherwise, run in the background thread + return await asyncio.wrap_future( + asyncio.run_coroutine_threadsafe(coro, self._loop) + ) + + def _run_as_sync(self, coro: Awaitable[T]) -> T: + """Run an async coroutine synchronously""" + if not self._loop: + raise Exception( + "Engine was initialized without a background loop and cannot call sync methods." + ) + return asyncio.run_coroutine_threadsafe(coro, self._loop).result() + + async def close(self) -> None: + """Dispose of connection pool""" + await self._run_as_async(self._pool.dispose()) + + def _escape_postgres_identifier(self, name: str) -> str: + return name.replace('"', '""') + + def _validate_column_dict(self, col: ColumnDict) -> None: + if not isinstance(col.get("name"), str): + raise TypeError("The 'name' field must be a string.") + if not isinstance(col.get("data_type"), str): + raise TypeError("The 'data_type' field must be a string.") + if not isinstance(col.get("nullable"), bool): + raise TypeError("The 'nullable' field must be a boolean.") + + async def _ainit_vectorstore_table( + self, + table_name: str, + vector_size: int, + *, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[Union[Column, ColumnDict]]] = None, + metadata_json_column: str = "langchain_metadata", + id_column: Union[str, Column, ColumnDict] = "langchain_id", + overwrite_existing: bool = False, + store_metadata: bool = True, + ) -> None: + """ + Create a table for saving of vectors to be used with PGVectorStore. + + Args: + table_name (str): The database table name. + vector_size (int): Vector size for the embedding model to be used. + schema_name (str): The schema name. + Default: "public". + content_column (str): Name of the column to store document content. + Default: "page_content". + embedding_column (str) : Name of the column to store vector embeddings. + Default: "embedding". + metadata_columns (Optional[list[Union[Column, ColumnDict]]]): A list of Columns to create for custom + metadata. Default: None. Optional. + metadata_json_column (str): The column to store extra metadata in JSON format. + Default: "langchain_metadata". Optional. + id_column (Union[str, Column, ColumnDict]) : Column to store ids. + Default: "langchain_id" column name with data type UUID. Optional. + overwrite_existing (bool): Whether to drop existing table. Default: False. + store_metadata (bool): Whether to store metadata in the table. + Default: True. + + Raises: + :class:`DuplicateTableError `: if table already exists. + :class:`UndefinedObjectError `: if the data type of the id column is not a postgreSQL data type. + """ + + schema_name = self._escape_postgres_identifier(schema_name) + table_name = self._escape_postgres_identifier(table_name) + content_column = self._escape_postgres_identifier(content_column) + embedding_column = self._escape_postgres_identifier(embedding_column) + if metadata_columns is None: + metadata_columns = [] + else: + for col in metadata_columns: + if isinstance(col, Column): + col.name = self._escape_postgres_identifier(col.name) + elif isinstance(col, dict): + self._validate_column_dict(col) + col["name"] = self._escape_postgres_identifier(col["name"]) + if isinstance(id_column, str): + id_column = self._escape_postgres_identifier(id_column) + elif isinstance(id_column, Column): + id_column.name = self._escape_postgres_identifier(id_column.name) + else: + self._validate_column_dict(id_column) + id_column["name"] = self._escape_postgres_identifier(id_column["name"]) + + async with self._pool.connect() as conn: + await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector")) + await conn.commit() + + if overwrite_existing: + async with self._pool.connect() as conn: + await conn.execute( + text(f'DROP TABLE IF EXISTS "{schema_name}"."{table_name}"') + ) + await conn.commit() + + if isinstance(id_column, str): + id_data_type = "UUID" + id_column_name = id_column + elif isinstance(id_column, Column): + id_data_type = id_column.data_type + id_column_name = id_column.name + else: + id_data_type = id_column["data_type"] + id_column_name = id_column["name"] + + query = f"""CREATE TABLE "{schema_name}"."{table_name}"( + "{id_column_name}" {id_data_type} PRIMARY KEY, + "{content_column}" TEXT NOT NULL, + "{embedding_column}" vector({vector_size}) NOT NULL""" + for column in metadata_columns: + if isinstance(column, Column): + nullable = "NOT NULL" if not column.nullable else "" + query += f',\n"{column.name}" {column.data_type} {nullable}' + elif isinstance(column, dict): + nullable = "NOT NULL" if not column["nullable"] else "" + query += f',\n"{column["name"]}" {column["data_type"]} {nullable}' + if store_metadata: + query += f""",\n"{metadata_json_column}" JSON""" + query += "\n);" + + async with self._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + async def ainit_vectorstore_table( + self, + table_name: str, + vector_size: int, + *, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[Union[Column, ColumnDict]]] = None, + metadata_json_column: str = "langchain_metadata", + id_column: Union[str, Column, ColumnDict] = "langchain_id", + overwrite_existing: bool = False, + store_metadata: bool = True, + ) -> None: + """ + Create a table for saving of vectors to be used with PGVectorStore. + + Args: + table_name (str): The database table name. + vector_size (int): Vector size for the embedding model to be used. + schema_name (str): The schema name. + Default: "public". + content_column (str): Name of the column to store document content. + Default: "page_content". + embedding_column (str) : Name of the column to store vector embeddings. + Default: "embedding". + metadata_columns (Optional[list[Union[Column, ColumnDict]]]): A list of Columns to create for custom + metadata. Default: None. Optional. + metadata_json_column (str): The column to store extra metadata in JSON format. + Default: "langchain_metadata". Optional. + id_column (Union[str, Column, ColumnDict]) : Column to store ids. + Default: "langchain_id" column name with data type UUID. Optional. + overwrite_existing (bool): Whether to drop existing table. Default: False. + store_metadata (bool): Whether to store metadata in the table. + Default: True. + """ + await self._run_as_async( + self._ainit_vectorstore_table( + table_name, + vector_size, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + overwrite_existing=overwrite_existing, + store_metadata=store_metadata, + ) + ) + + def init_vectorstore_table( + self, + table_name: str, + vector_size: int, + *, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[Union[Column, ColumnDict]]] = None, + metadata_json_column: str = "langchain_metadata", + id_column: Union[str, Column, ColumnDict] = "langchain_id", + overwrite_existing: bool = False, + store_metadata: bool = True, + ) -> None: + """ + Create a table for saving of vectors to be used with PGVectorStore. + + Args: + table_name (str): The database table name. + vector_size (int): Vector size for the embedding model to be used. + schema_name (str): The schema name. + Default: "public". + content_column (str): Name of the column to store document content. + Default: "page_content". + embedding_column (str) : Name of the column to store vector embeddings. + Default: "embedding". + metadata_columns (Optional[list[Union[Column, ColumnDict]]]): A list of Columns to create for custom + metadata. Default: None. Optional. + metadata_json_column (str): The column to store extra metadata in JSON format. + Default: "langchain_metadata". Optional. + id_column (Union[str, Column, ColumnDict]) : Column to store ids. + Default: "langchain_id" column name with data type UUID. Optional. + overwrite_existing (bool): Whether to drop existing table. Default: False. + store_metadata (bool): Whether to store metadata in the table. + Default: True. + """ + self._run_as_sync( + self._ainit_vectorstore_table( + table_name, + vector_size, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + overwrite_existing=overwrite_existing, + store_metadata=store_metadata, + ) + ) diff --git a/langchain_postgres/v2/indexes.py b/langchain_postgres/v2/indexes.py new file mode 100644 index 00000000..8dbcc4f7 --- /dev/null +++ b/langchain_postgres/v2/indexes.py @@ -0,0 +1,155 @@ +"""Index class to add vector indexes on the PGVectorStore. + +Learn more about vector indexes at https://github.com/pgvector/pgvector?tab=readme-ov-file#indexing +""" + +import enum +import re +import warnings +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import Optional + + +@dataclass +class StrategyMixin: + operator: str + search_function: str + index_function: str + + +class DistanceStrategy(StrategyMixin, enum.Enum): + """Enumerator of the Distance strategies.""" + + EUCLIDEAN = "<->", "l2_distance", "vector_l2_ops" + COSINE_DISTANCE = "<=>", "cosine_distance", "vector_cosine_ops" + INNER_PRODUCT = "<#>", "inner_product", "vector_ip_ops" + + +DEFAULT_DISTANCE_STRATEGY: DistanceStrategy = DistanceStrategy.COSINE_DISTANCE +DEFAULT_INDEX_NAME_SUFFIX: str = "langchainvectorindex" + + +def validate_identifier(identifier: str) -> None: + if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", identifier) is None: + raise ValueError( + f"Invalid identifier: {identifier}. Identifiers must start with a letter or underscore, and subsequent characters can be letters, digits, or underscores." + ) + + +@dataclass +class BaseIndex(ABC): + """ + Abstract base class for defining vector indexes. + + Attributes: + name (Optional[str]): A human-readable name for the index. Defaults to None. + index_type (str): A string identifying the type of index. Defaults to "base". + distance_strategy (DistanceStrategy): The strategy used to calculate distances + between vectors in the index. Defaults to DistanceStrategy.COSINE_DISTANCE. + partial_indexes (Optional[list[str]]): A list of names of partial indexes. Defaults to None. + extension_name (Optional[str]): The name of the extension to be created for the index, if any. Defaults to None. + """ + + name: Optional[str] = None + index_type: str = "base" + distance_strategy: DistanceStrategy = field( + default_factory=lambda: DistanceStrategy.COSINE_DISTANCE + ) + partial_indexes: Optional[list[str]] = None + extension_name: Optional[str] = None + + @abstractmethod + def index_options(self) -> str: + """Set index query options for vector store initialization.""" + raise NotImplementedError( + "index_options method must be implemented by subclass" + ) + + def get_index_function(self) -> str: + return self.distance_strategy.index_function + + def __post_init__(self) -> None: + """Check if initialization parameters are valid. + + Raises: + ValueError: extension_name is a valid postgreSQL identifier + """ + + if self.extension_name: + validate_identifier(self.extension_name) + if self.index_type: + validate_identifier(self.index_type) + + +@dataclass +class ExactNearestNeighbor(BaseIndex): + index_type: str = "exactnearestneighbor" + + +@dataclass +class QueryOptions(ABC): + @abstractmethod + def to_parameter(self) -> list[str]: + """Convert index attributes to list of configurations.""" + raise NotImplementedError("to_parameter method must be implemented by subclass") + + @abstractmethod + def to_string(self) -> str: + """Convert index attributes to string.""" + raise NotImplementedError("to_string method must be implemented by subclass") + + +@dataclass +class HNSWIndex(BaseIndex): + index_type: str = "hnsw" + m: int = 16 + ef_construction: int = 64 + + def index_options(self) -> str: + """Set index query options for vector store initialization.""" + return f"(m = {self.m}, ef_construction = {self.ef_construction})" + + +@dataclass +class HNSWQueryOptions(QueryOptions): + ef_search: int = 40 + + def to_parameter(self) -> list[str]: + """Convert index attributes to list of configurations.""" + return [f"hnsw.ef_search = {self.ef_search}"] + + def to_string(self) -> str: + """Convert index attributes to string.""" + warnings.warn( + "to_string is deprecated, use to_parameter instead.", + DeprecationWarning, + ) + return f"hnsw.ef_search = {self.ef_search}" + + +@dataclass +class IVFFlatIndex(BaseIndex): + index_type: str = "ivfflat" + lists: int = 100 + + def index_options(self) -> str: + """Set index query options for vector store initialization.""" + return f"(lists = {self.lists})" + + +@dataclass +class IVFFlatQueryOptions(QueryOptions): + probes: int = 1 + + def to_parameter(self) -> list[str]: + """Convert index attributes to list of configurations.""" + return [f"ivfflat.probes = {self.probes}"] + + def to_string(self) -> str: + """Convert index attributes to string.""" + warnings.warn( + "to_string is deprecated, use to_parameter instead.", + DeprecationWarning, + ) + return f"ivfflat.probes = {self.probes}" diff --git a/langchain_postgres/v2/vectorstores.py b/langchain_postgres/v2/vectorstores.py new file mode 100644 index 00000000..7f71d108 --- /dev/null +++ b/langchain_postgres/v2/vectorstores.py @@ -0,0 +1,842 @@ +# TODO: Remove below import when minimum supported Python version is 3.10 +from __future__ import annotations + +from typing import Any, Callable, Iterable, Optional, Sequence + +from langchain_core.documents import Document +from langchain_core.embeddings import Embeddings +from langchain_core.vectorstores import VectorStore + +from .async_vectorstore import AsyncPGVectorStore +from .engine import PGEngine +from .indexes import ( + DEFAULT_DISTANCE_STRATEGY, + BaseIndex, + DistanceStrategy, + QueryOptions, +) + + +class PGVectorStore(VectorStore): + """Postgres Vector Store class""" + + __create_key = object() + + def __init__(self, key: object, engine: PGEngine, vs: AsyncPGVectorStore): + """PGVectorStore constructor. + Args: + key (object): Prevent direct constructor usage. + engine (PGEngine): Connection pool engine for managing connections to Postgres database. + vs (AsyncPGVectorStore): The async only VectorStore implementation + + + Raises: + Exception: If called directly by user. + """ + if key != PGVectorStore.__create_key: + raise Exception( + "Only create class through 'create' or 'create_sync' methods!" + ) + + self._engine = engine + self.__vs = vs + + @classmethod + async def create( + cls: type[PGVectorStore], + engine: PGEngine, + embedding_service: Embeddings, + table_name: str, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: Optional[str] = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + ) -> PGVectorStore: + """Create an PGVectorStore instance. + + Args: + engine (PGEngine): Connection pool engine for managing connections to postgres database. + embedding_service (Embeddings): Text embedding model to use. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + content_column (str): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str]): Column(s) that represent a document's metadata. + ignore_metadata_columns (list[str]): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Returns: + PGVectorStore + """ + coro = AsyncPGVectorStore.create( + engine, + embedding_service, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + vs = await engine._run_as_async(coro) + return cls(cls.__create_key, engine, vs) + + @classmethod + def create_sync( + cls, + engine: PGEngine, + embedding_service: Embeddings, + table_name: str, + schema_name: str = "public", + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + ) -> PGVectorStore: + """Create an PGVectorStore instance. + + Args: + key (object): Prevent direct constructor usage. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + embedding_service (Embeddings): Text embedding model to use. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + content_column (str, optional): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str, optional): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str], optional): Column(s) that represent a document's metadata. Defaults to None. + ignore_metadata_columns (Optional[list[str]]): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str, optional): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str, optional): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy, optional): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int, optional): Number of Documents to return from search. Defaults to 4. + fetch_k (int, optional): Number of Documents to fetch to pass to MMR algorithm. Defaults to 20. + lambda_mult (float, optional): 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. + index_query_options (Optional[QueryOptions], optional): Index query option. Defaults to None. + + Returns: + PGVectorStore + """ + coro = AsyncPGVectorStore.create( + engine, + embedding_service, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + vs = engine._run_as_sync(coro) + return cls(cls.__create_key, engine, vs) + + @property + def embeddings(self) -> Embeddings: + return self.__vs.embedding_service + + async def aadd_embeddings( + self, + texts: Iterable[str], + embeddings: list[list[float]], + metadatas: Optional[list[dict]] = None, + ids: Optional[list[str]] = None, + **kwargs: Any, + ) -> list[str]: + """Add data along with embeddings to the table.""" + return await self._engine._run_as_async( + self.__vs.aadd_embeddings(texts, embeddings, metadatas, ids, **kwargs) + ) + + async def aadd_texts( + self, + texts: Iterable[str], + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Embed texts and add to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + return await self._engine._run_as_async( + self.__vs.aadd_texts(texts, metadatas, ids, **kwargs) + ) + + async def aadd_documents( + self, + documents: list[Document], + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Embed documents and add to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + return await self._engine._run_as_async( + self.__vs.aadd_documents(documents, ids, **kwargs) + ) + + def add_embeddings( + self, + texts: Iterable[str], + embeddings: list[list[float]], + metadatas: Optional[list[dict]] = None, + ids: Optional[list[str]] = None, + **kwargs: Any, + ) -> list[str]: + """Add data along with embeddings to the table.""" + return self._engine._run_as_sync( + self.__vs.aadd_embeddings(texts, embeddings, metadatas, ids, **kwargs) + ) + + def add_texts( + self, + texts: Iterable[str], + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Embed texts and add to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + return self._engine._run_as_sync( + self.__vs.aadd_texts(texts, metadatas, ids, **kwargs) + ) + + def add_documents( + self, + documents: list[Document], + ids: Optional[list] = None, + **kwargs: Any, + ) -> list[str]: + """Embed documents and add to the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + return self._engine._run_as_sync( + self.__vs.aadd_documents(documents, ids, **kwargs) + ) + + async def adelete( + self, + ids: Optional[list] = None, + **kwargs: Any, + ) -> Optional[bool]: + """Delete records from the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + return await self._engine._run_as_async(self.__vs.adelete(ids, **kwargs)) + + def delete( + self, + ids: Optional[list] = None, + **kwargs: Any, + ) -> Optional[bool]: + """Delete records from the table. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + """ + return self._engine._run_as_sync(self.__vs.adelete(ids, **kwargs)) + + @classmethod + async def afrom_texts( # type: ignore[override] + cls: type[PGVectorStore], + texts: list[str], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + schema_name: str = "public", + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + **kwargs: Any, + ) -> PGVectorStore: + """Create an PGVectorStore instance from texts. + + Args: + texts (list[str]): Texts to add to the vector store. + embedding (Embeddings): Text embedding model to use. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + metadatas (Optional[list[dict]], optional): List of metadatas to add to table records. Defaults to None. + ids: (Optional[list]): List of IDs to add to table records. Defaults to None. + content_column (str, optional): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str, optional): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str], optional): Column(s) that represent a document's metadata. Defaults to an empty list. + ignore_metadata_columns (Optional[list[str]], optional): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str, optional): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str, optional): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + + Returns: + PGVectorStore + """ + vs = await cls.create( + engine, + embedding, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + await vs.aadd_texts(texts, metadatas=metadatas, ids=ids) + return vs + + @classmethod + async def afrom_documents( # type: ignore[override] + cls: type[PGVectorStore], + documents: list[Document], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + schema_name: str = "public", + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + **kwargs: Any, + ) -> PGVectorStore: + """Create an PGVectorStore instance from documents. + + Args: + documents (list[Document]): Documents to add to the vector store. + embedding (Embeddings): Text embedding model to use. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + ids: (Optional[list]): List of IDs to add to table records. Defaults to None. + content_column (str, optional): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str, optional): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str], optional): Column(s) that represent a document's metadata. Defaults to an empty list. + ignore_metadata_columns (Optional[list[str]], optional): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str, optional): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str, optional): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + + Returns: + PGVectorStore + """ + + vs = await cls.create( + engine, + embedding, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + ) + await vs.aadd_documents(documents, ids=ids) + return vs + + @classmethod + def from_texts( # type: ignore[override] + cls: type[PGVectorStore], + texts: list[str], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + schema_name: str = "public", + metadatas: Optional[list[dict]] = None, + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + **kwargs: Any, + ) -> PGVectorStore: + """Create an PGVectorStore instance from texts. + + Args: + texts (list[str]): Texts to add to the vector store. + embedding (Embeddings): Text embedding model to use. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + metadatas (Optional[list[dict]], optional): List of metadatas to add to table records. Defaults to None. + ids: (Optional[list]): List of IDs to add to table records. Defaults to None. + content_column (str, optional): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str, optional): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str], optional): Column(s) that represent a document's metadata. Defaults to empty list. + ignore_metadata_columns (Optional[list[str]], optional): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str, optional): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str, optional): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + + Returns: + PGVectorStore + """ + vs = cls.create_sync( + engine, + embedding, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + **kwargs, + ) + vs.add_texts(texts, metadatas=metadatas, ids=ids) + return vs + + @classmethod + def from_documents( # type: ignore[override] + cls: type[PGVectorStore], + documents: list[Document], + embedding: Embeddings, + engine: PGEngine, + table_name: str, + schema_name: str = "public", + ids: Optional[list] = None, + content_column: str = "content", + embedding_column: str = "embedding", + metadata_columns: Optional[list[str]] = None, + ignore_metadata_columns: Optional[list[str]] = None, + id_column: str = "langchain_id", + metadata_json_column: str = "langchain_metadata", + distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY, + k: int = 4, + fetch_k: int = 20, + lambda_mult: float = 0.5, + index_query_options: Optional[QueryOptions] = None, + **kwargs: Any, + ) -> PGVectorStore: + """Create an PGVectorStore instance from documents. + + Args: + documents (list[Document]): Documents to add to the vector store. + embedding (Embeddings): Text embedding model to use. + engine (PGEngine): Connection pool engine for managing connections to postgres database. + table_name (str): Name of an existing table. + schema_name (str, optional): Name of the database schema. Defaults to "public". + ids: (Optional[list]): List of IDs to add to table records. Defaults to None. + content_column (str, optional): Column that represent a Document's page_content. Defaults to "content". + embedding_column (str, optional): Column for embedding vectors. The embedding is generated from the document value. Defaults to "embedding". + metadata_columns (list[str], optional): Column(s) that represent a document's metadata. Defaults to an empty list. + ignore_metadata_columns (Optional[list[str]], optional): Column(s) to ignore in pre-existing tables for a document's metadata. Can not be used with metadata_columns. Defaults to None. + id_column (str, optional): Column that represents the Document's id. Defaults to "langchain_id". + metadata_json_column (str, optional): Column to store metadata as JSON. Defaults to "langchain_metadata". + distance_strategy (DistanceStrategy): Distance strategy to use for vector similarity search. Defaults to COSINE_DISTANCE. + k (int): Number of Documents to return from search. Defaults to 4. + fetch_k (int): Number of Documents to fetch to pass to MMR algorithm. + lambda_mult (float): 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. + index_query_options (QueryOptions): Index query option. + + Raises: + :class:`InvalidTextRepresentationError `: if the `ids` data type does not match that of the `id_column`. + + Returns: + PGVectorStore + """ + vs = cls.create_sync( + engine, + embedding, + table_name, + schema_name=schema_name, + content_column=content_column, + embedding_column=embedding_column, + metadata_columns=metadata_columns, + ignore_metadata_columns=ignore_metadata_columns, + metadata_json_column=metadata_json_column, + id_column=id_column, + distance_strategy=distance_strategy, + k=k, + fetch_k=fetch_k, + lambda_mult=lambda_mult, + index_query_options=index_query_options, + **kwargs, + ) + vs.add_documents(documents, ids=ids) + return vs + + def similarity_search( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected by similarity search on query.""" + return self._engine._run_as_sync( + self.__vs.asimilarity_search(query, k, filter, **kwargs) + ) + + async def asimilarity_search( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected by similarity search on query.""" + return await self._engine._run_as_async( + self.__vs.asimilarity_search(query, k, filter, **kwargs) + ) + + # Required for (a)similarity_search_with_relevance_scores + def _select_relevance_score_fn(self) -> Callable[[float], float]: + """Select a relevance function based on distance strategy.""" + # Calculate distance strategy provided in vectorstore constructor + if self.__vs.distance_strategy == DistanceStrategy.COSINE_DISTANCE: + return self._cosine_relevance_score_fn + if self.__vs.distance_strategy == DistanceStrategy.INNER_PRODUCT: + return self._max_inner_product_relevance_score_fn + elif self.__vs.distance_strategy == DistanceStrategy.EUCLIDEAN: + return self._euclidean_relevance_score_fn + + async def asimilarity_search_with_score( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected by similarity search on query.""" + return await self._engine._run_as_async( + self.__vs.asimilarity_search_with_score(query, k, filter, **kwargs) + ) + + async def asimilarity_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected by vector similarity search.""" + return await self._engine._run_as_async( + self.__vs.asimilarity_search_by_vector(embedding, k, filter, **kwargs) + ) + + async def asimilarity_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected by vector similarity search.""" + return await self._engine._run_as_async( + self.__vs.asimilarity_search_with_score_by_vector( + embedding, k, filter, **kwargs + ) + ) + + async def amax_marginal_relevance_search( + self, + query: str, + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected using the maximal marginal relevance.""" + return await self._engine._run_as_async( + self.__vs.amax_marginal_relevance_search( + query, k, fetch_k, lambda_mult, filter, **kwargs + ) + ) + + async def amax_marginal_relevance_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected using the maximal marginal relevance.""" + return await self._engine._run_as_async( + self.__vs.amax_marginal_relevance_search_by_vector( + embedding, k, fetch_k, lambda_mult, filter, **kwargs + ) + ) + + async def amax_marginal_relevance_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected using the maximal marginal relevance.""" + return await self._engine._run_as_async( + self.__vs.amax_marginal_relevance_search_with_score_by_vector( + embedding, k, fetch_k, lambda_mult, filter, **kwargs + ) + ) + + def similarity_search_with_score( + self, + query: str, + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected by similarity search on query.""" + return self._engine._run_as_sync( + self.__vs.asimilarity_search_with_score(query, k, filter, **kwargs) + ) + + def similarity_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected by vector similarity search.""" + return self._engine._run_as_sync( + self.__vs.asimilarity_search_by_vector(embedding, k, filter, **kwargs) + ) + + def similarity_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected by similarity search on vector.""" + return self._engine._run_as_sync( + self.__vs.asimilarity_search_with_score_by_vector( + embedding, k, filter, **kwargs + ) + ) + + def max_marginal_relevance_search( + self, + query: str, + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected using the maximal marginal relevance.""" + return self._engine._run_as_sync( + self.__vs.amax_marginal_relevance_search( + query, k, fetch_k, lambda_mult, filter, **kwargs + ) + ) + + def max_marginal_relevance_search_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[Document]: + """Return docs selected using the maximal marginal relevance.""" + return self._engine._run_as_sync( + self.__vs.amax_marginal_relevance_search_by_vector( + embedding, k, fetch_k, lambda_mult, filter, **kwargs + ) + ) + + def max_marginal_relevance_search_with_score_by_vector( + self, + embedding: list[float], + k: Optional[int] = None, + fetch_k: Optional[int] = None, + lambda_mult: Optional[float] = None, + filter: Optional[dict] | Optional[str] = None, + **kwargs: Any, + ) -> list[tuple[Document, float]]: + """Return docs and distance scores selected using the maximal marginal relevance.""" + return self._engine._run_as_sync( + self.__vs.amax_marginal_relevance_search_with_score_by_vector( + embedding, k, fetch_k, lambda_mult, filter, **kwargs + ) + ) + + async def aapply_vector_index( + self, + index: BaseIndex, + name: Optional[str] = None, + concurrently: bool = False, + ) -> None: + """Create an index on the vector store table.""" + return await self._engine._run_as_async( + self.__vs.aapply_vector_index(index, name, concurrently=concurrently) + ) + + def apply_vector_index( + self, + index: BaseIndex, + name: Optional[str] = None, + concurrently: bool = False, + ) -> None: + """Create an index on the vector store table.""" + return self._engine._run_as_sync( + self.__vs.aapply_vector_index(index, name, concurrently=concurrently) + ) + + async def areindex(self, index_name: Optional[str] = None) -> None: + """Re-index the vector store table.""" + return await self._engine._run_as_async(self.__vs.areindex(index_name)) + + def reindex(self, index_name: Optional[str] = None) -> None: + """Re-index the vector store table.""" + return self._engine._run_as_sync(self.__vs.areindex(index_name)) + + async def adrop_vector_index( + self, + index_name: Optional[str] = None, + ) -> None: + """Drop the vector index.""" + return await self._engine._run_as_async( + self.__vs.adrop_vector_index(index_name) + ) + + def drop_vector_index( + self, + index_name: Optional[str] = None, + ) -> None: + """Drop the vector index.""" + return self._engine._run_as_sync(self.__vs.adrop_vector_index(index_name)) + + async def ais_valid_index( + self, + index_name: Optional[str] = None, + ) -> bool: + """Check if index exists in the table.""" + return await self._engine._run_as_async(self.__vs.is_valid_index(index_name)) + + def is_valid_index( + self, + index_name: Optional[str] = None, + ) -> bool: + """Check if index exists in the table.""" + return self._engine._run_as_sync(self.__vs.is_valid_index(index_name)) + + async def aget_by_ids(self, ids: Sequence[str]) -> list[Document]: + """Get documents by ids.""" + return await self._engine._run_as_async(self.__vs.aget_by_ids(ids=ids)) + + def get_by_ids(self, ids: Sequence[str]) -> list[Document]: + """Get documents by ids.""" + return self._engine._run_as_sync(self.__vs.aget_by_ids(ids=ids)) + + def get_table_name(self) -> str: + return self.__vs.table_name diff --git a/langchain_postgres/vectorstores.py b/langchain_postgres/vectorstores.py index 044bece6..3f741b8b 100644 --- a/langchain_postgres/vectorstores.py +++ b/langchain_postgres/vectorstores.py @@ -19,6 +19,7 @@ Type, Union, ) +import warnings from typing import ( cast as typing_cast, ) @@ -48,6 +49,8 @@ from langchain_postgres._utils import maximal_marginal_relevance +warnings.simplefilter("once", PendingDeprecationWarning) + class DistanceStrategy(str, enum.Enum): """Enumerator of the Distance strategies.""" @@ -428,6 +431,13 @@ def __init__( self._async_engine: Optional[AsyncEngine] = None self._async_init = False + warnings.warn( + "PGVector is being deprecated and will be removed in the future. " + "Please migrate to PGVectorStore. " + "Refer to the migration guide at [https://github.com/langchain-ai/langchain-postgres/blob/main/examples/migrate_pgvector_to_pgvectorstore.md] for details.", + PendingDeprecationWarning, + ) + if isinstance(connection, str): if async_mode: self._async_engine = create_async_engine( diff --git a/poetry.lock b/poetry.lock index a648c81d..84e6b922 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,13 +42,13 @@ files = [ [[package]] name = "anyio" -version = "4.8.0" +version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, - {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] @@ -58,8 +58,8 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -164,22 +164,99 @@ files = [ astroid = ["astroid (>=2,<4)"] test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "asyncpg" +version = "0.30.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"}, + {file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"}, + {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f"}, + {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af"}, + {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75"}, + {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f"}, + {file = "asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf"}, + {file = "asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50"}, + {file = "asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a"}, + {file = "asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed"}, + {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a"}, + {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956"}, + {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056"}, + {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454"}, + {file = "asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d"}, + {file = "asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f"}, + {file = "asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e"}, + {file = "asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a"}, + {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3"}, + {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737"}, + {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a"}, + {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af"}, + {file = "asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e"}, + {file = "asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305"}, + {file = "asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70"}, + {file = "asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3"}, + {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33"}, + {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4"}, + {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4"}, + {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba"}, + {file = "asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590"}, + {file = "asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e"}, + {file = "asyncpg-0.30.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ff1fc8b5bf724273782ff8b4f57b0f8220a1b2324184846b39d1ab4122031d"}, + {file = "asyncpg-0.30.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64e899bce0600871b55368b8483e5e3e7f1860c9482e7f12e0a771e747988168"}, + {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b290f4726a887f75dcd1b3006f484252db37602313f806e9ffc4e5996cfe5cb"}, + {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86b0e2cd3f1249d6fe6fd6cfe0cd4538ba994e2d8249c0491925629b9104d0f"}, + {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:393af4e3214c8fa4c7b86da6364384c0d1b3298d45803375572f415b6f673f38"}, + {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34"}, + {file = "asyncpg-0.30.0-cp38-cp38-win32.whl", hash = "sha256:0b448f0150e1c3b96cb0438a0d0aa4871f1472e58de14a3ec320dbb2798fb0d4"}, + {file = "asyncpg-0.30.0-cp38-cp38-win_amd64.whl", hash = "sha256:f23b836dd90bea21104f69547923a02b167d999ce053f3d502081acea2fba15b"}, + {file = "asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad"}, + {file = "asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff"}, + {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708"}, + {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144"}, + {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb"}, + {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547"}, + {file = "asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a"}, + {file = "asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773"}, + {file = "asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.11.0\""} + +[package.extras] +docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"] +gssauth = ["gssapi", "sspilib"] +test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"] + [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" files = [ - {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, - {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, + {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", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -475,73 +552,74 @@ test = ["pytest"] [[package]] name = "coverage" -version = "7.6.10" +version = "7.8.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, - {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, - {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, - {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, - {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, - {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, - {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, - {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, - {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, - {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, - {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, - {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, - {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, - {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, - {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, - {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, - {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, - {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, - {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, - {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, - {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, - {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, - {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, - {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, - {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, - {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, - {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, - {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, - {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, - {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, - {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, - {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, - {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, + {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, + {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, + {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, + {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, + {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, + {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, + {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, + {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, + {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, + {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, + {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, + {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, + {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, + {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, + {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, ] [package.dependencies] @@ -552,48 +630,48 @@ toml = ["tomli"] [[package]] name = "debugpy" -version = "1.8.12" +version = "1.8.13" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.12-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a"}, - {file = "debugpy-1.8.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45"}, - {file = "debugpy-1.8.12-cp310-cp310-win32.whl", hash = "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c"}, - {file = "debugpy-1.8.12-cp310-cp310-win_amd64.whl", hash = "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9"}, - {file = "debugpy-1.8.12-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5"}, - {file = "debugpy-1.8.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7"}, - {file = "debugpy-1.8.12-cp311-cp311-win32.whl", hash = "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb"}, - {file = "debugpy-1.8.12-cp311-cp311-win_amd64.whl", hash = "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1"}, - {file = "debugpy-1.8.12-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498"}, - {file = "debugpy-1.8.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06"}, - {file = "debugpy-1.8.12-cp312-cp312-win32.whl", hash = "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d"}, - {file = "debugpy-1.8.12-cp312-cp312-win_amd64.whl", hash = "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969"}, - {file = "debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f"}, - {file = "debugpy-1.8.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9"}, - {file = "debugpy-1.8.12-cp313-cp313-win32.whl", hash = "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180"}, - {file = "debugpy-1.8.12-cp313-cp313-win_amd64.whl", hash = "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c"}, - {file = "debugpy-1.8.12-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:b0232cd42506d0c94f9328aaf0d1d0785f90f87ae72d9759df7e5051be039738"}, - {file = "debugpy-1.8.12-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af40506a59450f1315168d47a970db1a65aaab5df3833ac389d2899a5d63b3f"}, - {file = "debugpy-1.8.12-cp38-cp38-win32.whl", hash = "sha256:5cc45235fefac57f52680902b7d197fb2f3650112379a6fa9aa1b1c1d3ed3f02"}, - {file = "debugpy-1.8.12-cp38-cp38-win_amd64.whl", hash = "sha256:557cc55b51ab2f3371e238804ffc8510b6ef087673303890f57a24195d096e61"}, - {file = "debugpy-1.8.12-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:b5c6c967d02fee30e157ab5227706f965d5c37679c687b1e7bbc5d9e7128bd41"}, - {file = "debugpy-1.8.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a77f422f31f170c4b7e9ca58eae2a6c8e04da54121900651dfa8e66c29901a"}, - {file = "debugpy-1.8.12-cp39-cp39-win32.whl", hash = "sha256:a4042edef80364239f5b7b5764e55fd3ffd40c32cf6753da9bda4ff0ac466018"}, - {file = "debugpy-1.8.12-cp39-cp39-win_amd64.whl", hash = "sha256:f30b03b0f27608a0b26c75f0bb8a880c752c0e0b01090551b9d87c7d783e2069"}, - {file = "debugpy-1.8.12-py2.py3-none-any.whl", hash = "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6"}, - {file = "debugpy-1.8.12.tar.gz", hash = "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce"}, + {file = "debugpy-1.8.13-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:06859f68e817966723ffe046b896b1bd75c665996a77313370336ee9e1de3e90"}, + {file = "debugpy-1.8.13-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c2db69fb8df3168bc857d7b7d2494fed295dfdbde9a45f27b4b152f37520"}, + {file = "debugpy-1.8.13-cp310-cp310-win32.whl", hash = "sha256:46abe0b821cad751fc1fb9f860fb2e68d75e2c5d360986d0136cd1db8cad4428"}, + {file = "debugpy-1.8.13-cp310-cp310-win_amd64.whl", hash = "sha256:dc7b77f5d32674686a5f06955e4b18c0e41fb5a605f5b33cf225790f114cfeec"}, + {file = "debugpy-1.8.13-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:eee02b2ed52a563126c97bf04194af48f2fe1f68bb522a312b05935798e922ff"}, + {file = "debugpy-1.8.13-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4caca674206e97c85c034c1efab4483f33971d4e02e73081265ecb612af65377"}, + {file = "debugpy-1.8.13-cp311-cp311-win32.whl", hash = "sha256:7d9a05efc6973b5aaf076d779cf3a6bbb1199e059a17738a2aa9d27a53bcc888"}, + {file = "debugpy-1.8.13-cp311-cp311-win_amd64.whl", hash = "sha256:62f9b4a861c256f37e163ada8cf5a81f4c8d5148fc17ee31fb46813bd658cdcc"}, + {file = "debugpy-1.8.13-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:2b8de94c5c78aa0d0ed79023eb27c7c56a64c68217d881bee2ffbcb13951d0c1"}, + {file = "debugpy-1.8.13-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887d54276cefbe7290a754424b077e41efa405a3e07122d8897de54709dbe522"}, + {file = "debugpy-1.8.13-cp312-cp312-win32.whl", hash = "sha256:3872ce5453b17837ef47fb9f3edc25085ff998ce63543f45ba7af41e7f7d370f"}, + {file = "debugpy-1.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:63ca7670563c320503fea26ac688988d9d6b9c6a12abc8a8cf2e7dd8e5f6b6ea"}, + {file = "debugpy-1.8.13-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:31abc9618be4edad0b3e3a85277bc9ab51a2d9f708ead0d99ffb5bb750e18503"}, + {file = "debugpy-1.8.13-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0bd87557f97bced5513a74088af0b84982b6ccb2e254b9312e29e8a5c4270eb"}, + {file = "debugpy-1.8.13-cp313-cp313-win32.whl", hash = "sha256:5268ae7fdca75f526d04465931cb0bd24577477ff50e8bb03dab90983f4ebd02"}, + {file = "debugpy-1.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:79ce4ed40966c4c1631d0131606b055a5a2f8e430e3f7bf8fd3744b09943e8e8"}, + {file = "debugpy-1.8.13-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:acf39a6e98630959763f9669feddee540745dfc45ad28dbc9bd1f9cd60639391"}, + {file = "debugpy-1.8.13-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:924464d87e7d905eb0d79fb70846558910e906d9ee309b60c4fe597a2e802590"}, + {file = "debugpy-1.8.13-cp38-cp38-win32.whl", hash = "sha256:3dae443739c6b604802da9f3e09b0f45ddf1cf23c99161f3a1a8039f61a8bb89"}, + {file = "debugpy-1.8.13-cp38-cp38-win_amd64.whl", hash = "sha256:ed93c3155fc1f888ab2b43626182174e457fc31b7781cd1845629303790b8ad1"}, + {file = "debugpy-1.8.13-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:6fab771639332bd8ceb769aacf454a30d14d7a964f2012bf9c4e04c60f16e85b"}, + {file = "debugpy-1.8.13-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32b6857f8263a969ce2ca098f228e5cc0604d277447ec05911a8c46cf3e7e307"}, + {file = "debugpy-1.8.13-cp39-cp39-win32.whl", hash = "sha256:f14d2c4efa1809da125ca62df41050d9c7cd9cb9e380a2685d1e453c4d450ccb"}, + {file = "debugpy-1.8.13-cp39-cp39-win_amd64.whl", hash = "sha256:ea869fe405880327497e6945c09365922c79d2a1eed4c3ae04d77ac7ae34b2b5"}, + {file = "debugpy-1.8.13-py2.py3-none-any.whl", hash = "sha256:d4ba115cdd0e3a70942bd562adba9ec8c651fe69ddde2298a1be296fc331906f"}, + {file = "debugpy-1.8.13.tar.gz", hash = "sha256:837e7bef95bdefba426ae38b9a94821ebdc5bea55627879cd48165c90b9e50ce"}, ] [[package]] name = "decorator" -version = "5.1.1" +version = "5.2.1" description = "Decorators for Humans" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, ] [[package]] @@ -852,13 +930,13 @@ type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -977,13 +1055,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, - {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ] [package.dependencies] @@ -1324,13 +1402,13 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v [[package]] name = "langchain-core" -version = "0.3.33" +version = "0.3.49" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langchain_core-0.3.33-py3-none-any.whl", hash = "sha256:269706408a2223f863ff1f9616f31903a5712403199d828b50aadbc4c28b553a"}, - {file = "langchain_core-0.3.33.tar.gz", hash = "sha256:b5dd93a4e7f8198d2fc6048723b0bfecf7aaf128b0d268cbac19c34c1579b953"}, + {file = "langchain_core-0.3.49-py3-none-any.whl", hash = "sha256:893ee42c9af13bf2a2d8c2ec15ba00a5c73cccde21a2bd005234ee0e78a2bdf8"}, + {file = "langchain_core-0.3.49.tar.gz", hash = "sha256:d9dbff9bac0021463a986355c13864d6a68c41f8559dbbd399a68e1ebd9b04b9"}, ] [package.dependencies] @@ -1370,18 +1448,19 @@ syrupy = ">=4,<5" [[package]] name = "langsmith" -version = "0.3.5" +version = "0.3.21" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.9" files = [ - {file = "langsmith-0.3.5-py3-none-any.whl", hash = "sha256:29da924d2e3662dd56f96d179ebc06662b66dd0b2317362ccebe0de1b78750e7"}, - {file = "langsmith-0.3.5.tar.gz", hash = "sha256:d891a205f70ab0b2c26311db6c52486ffc9fc1124238b999619445f6ae900725"}, + {file = "langsmith-0.3.21-py3-none-any.whl", hash = "sha256:1ff6b28a8cfb5a4ff4a45c812072f16abdbae544b5d67fa275388c504b5a73cc"}, + {file = "langsmith-0.3.21.tar.gz", hash = "sha256:71e30c11c6151e5fe73226d4865a78e25ac4c226c3ea7a524be5adc85ad9f97e"}, ] [package.dependencies] httpx = ">=0.23.0,<1" orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} +packaging = ">=23.2" pydantic = [ {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, @@ -1392,6 +1471,8 @@ 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]] @@ -1480,13 +1561,13 @@ traitlets = "*" [[package]] name = "mistune" -version = "3.1.1" +version = "3.1.3" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false python-versions = ">=3.8" files = [ - {file = "mistune-3.1.1-py3-none-any.whl", hash = "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a"}, - {file = "mistune-3.1.1.tar.gz", hash = "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c"}, + {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, + {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, ] [package.dependencies] @@ -1494,49 +1575,43 @@ typing-extensions = {version = "*", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "1.14.1" +version = "1.15.0" description = "Optional static typing for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, - {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, - {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, - {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, - {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, - {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, - {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, - {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, - {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, - {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, - {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, - {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, - {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, - {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, - {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, - {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, - {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, - {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, - {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, - {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, - {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, - {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, - {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, - {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, - {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, - {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, - {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, + {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, + {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, + {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, + {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, + {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, + {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, + {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, + {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, + {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, + {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, + {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, + {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, + {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, + {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, + {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, + {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, + {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, + {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, + {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, + {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, + {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, + {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, + {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, + {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, + {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, + {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, + {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, + {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, + {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, + {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, + {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, + {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, ] [package.dependencies] @@ -1771,146 +1846,81 @@ files = [ {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] -[[package]] -name = "numpy" -version = "2.0.2" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -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 = "orjson" -version = "3.10.15" +version = "3.10.16" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"}, - {file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"}, - {file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"}, - {file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"}, - {file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"}, - {file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"}, - {file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"}, - {file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"}, - {file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"}, - {file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"}, - {file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"}, - {file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"}, - {file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"}, - {file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"}, - {file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"}, - {file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"}, - {file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"}, - {file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"}, - {file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"}, - {file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"}, - {file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"}, - {file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"}, - {file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"}, - {file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"}, - {file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"}, - {file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"}, - {file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"}, - {file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"}, - {file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"}, - {file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"}, - {file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"}, + {file = "orjson-3.10.16-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4cb473b8e79154fa778fb56d2d73763d977be3dcc140587e07dbc545bbfc38f8"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622a8e85eeec1948690409a19ca1c7d9fd8ff116f4861d261e6ae2094fe59a00"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c682d852d0ce77613993dc967e90e151899fe2d8e71c20e9be164080f468e370"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c520ae736acd2e32df193bcff73491e64c936f3e44a2916b548da048a48b46b"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:134f87c76bfae00f2094d85cfab261b289b76d78c6da8a7a3b3c09d362fd1e06"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59afde79563e2cf37cfe62ee3b71c063fd5546c8e662d7fcfc2a3d5031a5c4c"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113602f8241daaff05d6fad25bd481d54c42d8d72ef4c831bb3ab682a54d9e15"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4fc0077d101f8fab4031e6554fc17b4c2ad8fdbc56ee64a727f3c95b379e31da"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9c6bf6ff180cd69e93f3f50380224218cfab79953a868ea3908430bcfaf9cb5e"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5673eadfa952f95a7cd76418ff189df11b0a9c34b1995dff43a6fdbce5d63bf4"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fe638a423d852b0ae1e1a79895851696cb0d9fa0946fdbfd5da5072d9bb9551"}, + {file = "orjson-3.10.16-cp310-cp310-win32.whl", hash = "sha256:33af58f479b3c6435ab8f8b57999874b4b40c804c7a36b5cc6b54d8f28e1d3dd"}, + {file = "orjson-3.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:0338356b3f56d71293c583350af26f053017071836b07e064e92819ecf1aa055"}, + {file = "orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164"}, + {file = "orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619"}, + {file = "orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60"}, + {file = "orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca"}, + {file = "orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e"}, + {file = "orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c"}, + {file = "orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6"}, + {file = "orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd"}, + {file = "orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5"}, + {file = "orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6"}, + {file = "orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7"}, + {file = "orjson-3.10.16-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c35b5c1fb5a5d6d2fea825dec5d3d16bea3c06ac744708a8e1ff41d4ba10cdf1"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9aac7ecc86218b4b3048c768f227a9452287001d7548500150bb75ee21bf55d"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e19f5102fff36f923b6dfdb3236ec710b649da975ed57c29833cb910c5a73ab"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17210490408eb62755a334a6f20ed17c39f27b4f45d89a38cd144cd458eba80b"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbbe04451db85916e52a9f720bd89bf41f803cf63b038595674691680cbebd1b"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a966eba501a3a1f309f5a6af32ed9eb8f316fa19d9947bac3e6350dc63a6f0a"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e0d22f06c81e6c435723343e1eefc710e0510a35d897856766d475f2a15687"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7c1e602d028ee285dbd300fb9820b342b937df64d5a3336e1618b354e95a2569"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d230e5020666a6725629df81e210dc11c3eae7d52fe909a7157b3875238484f3"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f8baac07d4555f57d44746a7d80fbe6b2c4fe2ed68136b4abb51cfec512a5e9"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:524e48420b90fc66953e91b660b3d05faaf921277d6707e328fde1c218b31250"}, + {file = "orjson-3.10.16-cp39-cp39-win32.whl", hash = "sha256:a9f614e31423d7292dbca966a53b2d775c64528c7d91424ab2747d8ab8ce5c72"}, + {file = "orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905"}, + {file = "orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10"}, ] [[package]] @@ -1991,19 +2001,19 @@ numpy = "*" [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -2050,43 +2060,36 @@ wcwidth = "*" [[package]] name = "psutil" -version = "6.1.1" -description = "Cross-platform lib for process and system monitoring in Python." +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" files = [ - {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, - {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"}, - {file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"}, - {file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"}, - {file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"}, - {file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"}, - {file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"}, - {file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"}, - {file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"}, - {file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"}, - {file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"}, - {file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"}, -] - -[package.extras] -dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "psycopg" -version = "3.2.4" +version = "3.2.6" description = "PostgreSQL database adapter for Python" optional = false python-versions = ">=3.8" files = [ - {file = "psycopg-3.2.4-py3-none-any.whl", hash = "sha256:43665368ccd48180744cab26b74332f46b63b7e06e8ce0775547a3533883d381"}, - {file = "psycopg-3.2.4.tar.gz", hash = "sha256:f26f1346d6bf1ef5f5ef1714dd405c67fb365cfd1c6cea07de1792747b167b92"}, + {file = "psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58"}, + {file = "psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a"}, ] [package.dependencies] @@ -2094,22 +2097,22 @@ typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.2.4)"] -c = ["psycopg-c (==3.2.4)"] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] +binary = ["psycopg-binary (==3.2.6)"] +c = ["psycopg-c (==3.2.6)"] +dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] pool = ["psycopg-pool"] test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] [[package]] name = "psycopg-pool" -version = "3.2.4" +version = "3.2.6" description = "Connection Pool for Psycopg" optional = false python-versions = ">=3.8" files = [ - {file = "psycopg_pool-3.2.4-py3-none-any.whl", hash = "sha256:f6a22cff0f21f06d72fb2f5cb48c618946777c49385358e0c88d062c59cbd224"}, - {file = "psycopg_pool-3.2.4.tar.gz", hash = "sha256:61774b5bbf23e8d22bedc7504707135aaf744679f8ef9b3fe29942920746a6ed"}, + {file = "psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7"}, + {file = "psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5"}, ] [package.dependencies] @@ -2153,19 +2156,20 @@ files = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.1" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, + {file = "pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8"}, + {file = "pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" +pydantic-core = "2.33.0" typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -2173,111 +2177,110 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.0" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pydantic_core-2.33.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71dffba8fe9ddff628c68f3abd845e91b028361d43c5f8e7b3f8b91d7d85413e"}, + {file = "pydantic_core-2.33.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:abaeec1be6ed535a5d7ffc2e6c390083c425832b20efd621562fbb5bff6dc518"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759871f00e26ad3709efc773ac37b4d571de065f9dfb1778012908bcc36b3a73"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcfebee69cd5e1c0b76a17e17e347c84b00acebb8dd8edb22d4a03e88e82a207"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b1262b912435a501fa04cd213720609e2cefa723a07c92017d18693e69bf00b"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4726f1f3f42d6a25678c67da3f0b10f148f5655813c5aca54b0d1742ba821b8f"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e790954b5093dff1e3a9a2523fddc4e79722d6f07993b4cd5547825c3cbf97b5"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34e7fb3abe375b5c4e64fab75733d605dda0f59827752debc99c17cb2d5f3276"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecb158fb9b9091b515213bed3061eb7deb1d3b4e02327c27a0ea714ff46b0760"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:4d9149e7528af8bbd76cc055967e6e04617dcb2a2afdaa3dea899406c5521faa"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e81a295adccf73477220e15ff79235ca9dcbcee4be459eb9d4ce9a2763b8386c"}, + {file = "pydantic_core-2.33.0-cp310-cp310-win32.whl", hash = "sha256:f22dab23cdbce2005f26a8f0c71698457861f97fc6318c75814a50c75e87d025"}, + {file = "pydantic_core-2.33.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cb2390355ba084c1ad49485d18449b4242da344dea3e0fe10babd1f0db7dcfc"}, + {file = "pydantic_core-2.33.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a608a75846804271cf9c83e40bbb4dab2ac614d33c6fd5b0c6187f53f5c593ef"}, + {file = "pydantic_core-2.33.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1c69aa459f5609dec2fa0652d495353accf3eda5bdb18782bc5a2ae45c9273a"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ec80eb5a5f45a2211793f1c4aeddff0c3761d1c70d684965c1807e923a588b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e925819a98318d17251776bd3d6aa9f3ff77b965762155bdad15d1a9265c4cfd"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bf68bb859799e9cec3d9dd8323c40c00a254aabb56fe08f907e437005932f2b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b2ea72dea0825949a045fa4071f6d5b3d7620d2a208335207793cf29c5a182d"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1583539533160186ac546b49f5cde9ffc928062c96920f58bd95de32ffd7bffd"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23c3e77bf8a7317612e5c26a3b084c7edeb9552d645742a54a5867635b4f2453"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7a7f2a3f628d2f7ef11cb6188bcf0b9e1558151d511b974dfea10a49afe192b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f1fb026c575e16f673c61c7b86144517705865173f3d0907040ac30c4f9f5915"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:635702b2fed997e0ac256b2cfbdb4dd0bf7c56b5d8fba8ef03489c03b3eb40e2"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win32.whl", hash = "sha256:07b4ced28fccae3f00626eaa0c4001aa9ec140a29501770a88dbbb0966019a86"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win_amd64.whl", hash = "sha256:4927564be53239a87770a5f86bdc272b8d1fbb87ab7783ad70255b4ab01aa25b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win_arm64.whl", hash = "sha256:69297418ad644d521ea3e1aa2e14a2a422726167e9ad22b89e8f1130d68e1e9a"}, + {file = "pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43"}, + {file = "pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6"}, + {file = "pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555"}, + {file = "pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7"}, + {file = "pydantic_core-2.33.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7c9c84749f5787781c1c45bb99f433402e484e515b40675a5d121ea14711cf61"}, + {file = "pydantic_core-2.33.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:64672fa888595a959cfeff957a654e947e65bbe1d7d82f550417cbd6898a1d6b"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bc7367c0961dec292244ef2549afa396e72e28cc24706210bd44d947582c59"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce72d46eb201ca43994303025bd54d8a35a3fc2a3495fac653d6eb7205ce04f4"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14229c1504287533dbf6b1fc56f752ce2b4e9694022ae7509631ce346158de11"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:085d8985b1c1e48ef271e98a658f562f29d89bda98bf120502283efbc87313eb"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31860fbda80d8f6828e84b4a4d129fd9c4535996b8249cfb8c720dc2a1a00bb8"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f200b2f20856b5a6c3a35f0d4e344019f805e363416e609e9b47c552d35fd5ea"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f72914cfd1d0176e58ddc05c7a47674ef4222c8253bf70322923e73e14a4ac3"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:91301a0980a1d4530d4ba7e6a739ca1a6b31341252cb709948e0aca0860ce0ae"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7419241e17c7fbe5074ba79143d5523270e04f86f1b3a0dff8df490f84c8273a"}, + {file = "pydantic_core-2.33.0-cp39-cp39-win32.whl", hash = "sha256:7a25493320203005d2a4dac76d1b7d953cb49bce6d459d9ae38e30dd9f29bc9c"}, + {file = "pydantic_core-2.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:82a4eba92b7ca8af1b7d5ef5f3d9647eee94d1f74d21ca7c21e3a2b92e008358"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2762c568596332fdab56b07060c8ab8362c56cf2a339ee54e491cd503612c50"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bf637300ff35d4f59c006fff201c510b2b5e745b07125458a5389af3c0dff8c"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c151ce3d59ed56ebd7ce9ce5986a409a85db697d25fc232f8e81f195aa39a1"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee65f0cc652261744fd07f2c6e6901c914aa6c5ff4dcfaf1136bc394d0dd26b"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:024d136ae44d233e6322027bbf356712b3940bee816e6c948ce4b90f18471b3d"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e37f10f6d4bc67c58fbd727108ae1d8b92b397355e68519f1e4a7babb1473442"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:502ed542e0d958bd12e7c3e9a015bce57deaf50eaa8c2e1c439b512cb9db1e3a"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:715c62af74c236bf386825c0fdfa08d092ab0f191eb5b4580d11c3189af9d330"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bccc06fa0372151f37f6b69834181aa9eb57cf8665ed36405fb45fbf6cac3bae"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d8dc9f63a26f7259b57f46a7aab5af86b2ad6fbe48487500bb1f4b27e051e4c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30369e54d6d0113d2aa5aee7a90d17f225c13d87902ace8fcd7bbf99b19124db"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb479354c62067afa62f53bb387827bee2f75c9c79ef25eef6ab84d4b1ae3b"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0310524c833d91403c960b8a3cf9f46c282eadd6afd276c8c5edc617bd705dc9"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eddb18a00bbb855325db27b4c2a89a4ba491cd6a0bd6d852b225172a1f54b36c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ade5dbcf8d9ef8f4b28e682d0b29f3008df9842bb5ac48ac2c17bc55771cc976"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2c0afd34f928383e3fd25740f2050dbac9d077e7ba5adbaa2227f4d4f3c8da5c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7da333f21cd9df51d5731513a6d39319892947604924ddf2e24a4612975fb936"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b6d77c75a57f041c5ee915ff0b0bb58eabb78728b69ed967bc5b780e8f701b8"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba95691cf25f63df53c1d342413b41bd7762d9acb425df8858d7efa616c0870e"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f1ab031feb8676f6bd7c85abec86e2935850bf19b84432c64e3e239bffeb1ec"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c1151827eef98b83d49b6ca6065575876a02d2211f259fb1a6b7757bd24dd8"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66d931ea2c1464b738ace44b7334ab32a2fd50be023d863935eb00f42be1778"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bcf0bab28995d483f6c8d7db25e0d05c3efa5cebfd7f56474359e7137f39856"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:89670d7a0045acb52be0566df5bc8b114ac967c662c06cf5e0c606e4aadc964b"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:b716294e721d8060908dbebe32639b01bfe61b15f9f57bcc18ca9a0e00d9520b"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fc53e05c16697ff0c1c7c2b98e45e131d4bfb78068fffff92a82d169cbb4c7b7"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:68504959253303d3ae9406b634997a2123a0b0c1da86459abbd0ffc921695eac"}, + {file = "pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3"}, ] [package.dependencies] @@ -2399,46 +2402,44 @@ six = ">=1.5" [[package]] name = "python-json-logger" -version = "3.2.1" +version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" files = [ - {file = "python_json_logger-3.2.1-py3-none-any.whl", hash = "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090"}, - {file = "python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008"}, + {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, + {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, ] [package.dependencies] typing_extensions = {version = "*", markers = "python_version < \"3.10\""} [package.extras] -dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "msgspec-python313-pre", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] +dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] [[package]] name = "pywin32" -version = "308" +version = "310" description = "Python for Window Extensions" optional = false python-versions = "*" files = [ - {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, - {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, - {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"}, - {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"}, - {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"}, - {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"}, - {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"}, - {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"}, - {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"}, - {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"}, - {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"}, - {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"}, - {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"}, - {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"}, - {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"}, - {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"}, - {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"}, - {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"}, + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, ] [[package]] @@ -2521,120 +2522,104 @@ files = [ [[package]] name = "pyzmq" -version = "26.2.1" +version = "26.3.0" description = "Python bindings for 0MQ" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pyzmq-26.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb"}, - {file = "pyzmq-26.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24"}, - {file = "pyzmq-26.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459"}, - {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c"}, - {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e"}, - {file = "pyzmq-26.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3"}, - {file = "pyzmq-26.2.1-cp310-cp310-win32.whl", hash = "sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa"}, - {file = "pyzmq-26.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473"}, - {file = "pyzmq-26.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594"}, - {file = "pyzmq-26.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a"}, - {file = "pyzmq-26.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4"}, - {file = "pyzmq-26.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa"}, - {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f"}, - {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba"}, - {file = "pyzmq-26.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd"}, - {file = "pyzmq-26.2.1-cp311-cp311-win32.whl", hash = "sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7"}, - {file = "pyzmq-26.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1"}, - {file = "pyzmq-26.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7"}, - {file = "pyzmq-26.2.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3"}, - {file = "pyzmq-26.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da"}, - {file = "pyzmq-26.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435"}, - {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a"}, - {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4"}, - {file = "pyzmq-26.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e"}, - {file = "pyzmq-26.2.1-cp312-cp312-win32.whl", hash = "sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a"}, - {file = "pyzmq-26.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13"}, - {file = "pyzmq-26.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5"}, - {file = "pyzmq-26.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23"}, - {file = "pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab"}, - {file = "pyzmq-26.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce"}, - {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a"}, - {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9"}, - {file = "pyzmq-26.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad"}, - {file = "pyzmq-26.2.1-cp313-cp313-win32.whl", hash = "sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb"}, - {file = "pyzmq-26.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf"}, - {file = "pyzmq-26.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce"}, - {file = "pyzmq-26.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e"}, - {file = "pyzmq-26.2.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3"}, - {file = "pyzmq-26.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e"}, - {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7"}, - {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8"}, - {file = "pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460"}, - {file = "pyzmq-26.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3ef584f13820d2629326fe20cc04069c21c5557d84c26e277cfa6235e523b10f"}, - {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:160194d1034902937359c26ccfa4e276abffc94937e73add99d9471e9f555dd6"}, - {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:574b285150afdbf0a0424dddf7ef9a0d183988eb8d22feacb7160f7515e032cb"}, - {file = "pyzmq-26.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44dba28c34ce527cf687156c81f82bf1e51f047838d5964f6840fd87dfecf9fe"}, - {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9fbdb90b85c7624c304f72ec7854659a3bd901e1c0ffb2363163779181edeb68"}, - {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a7ad34a2921e8f76716dc7205c9bf46a53817e22b9eec2e8a3e08ee4f4a72468"}, - {file = "pyzmq-26.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:866c12b7c90dd3a86983df7855c6f12f9407c8684db6aa3890fc8027462bda82"}, - {file = "pyzmq-26.2.1-cp37-cp37m-win32.whl", hash = "sha256:eeb37f65350d5c5870517f02f8bbb2ac0fbec7b416c0f4875219fef305a89a45"}, - {file = "pyzmq-26.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4eb3197f694dfb0ee6af29ef14a35f30ae94ff67c02076eef8125e2d98963cd0"}, - {file = "pyzmq-26.2.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:36d4e7307db7c847fe37413f333027d31c11d5e6b3bacbb5022661ac635942ba"}, - {file = "pyzmq-26.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1c6ae0e95d0a4b0cfe30f648a18e764352d5415279bdf34424decb33e79935b8"}, - {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5b4fc44f5360784cc02392f14235049665caaf7c0fe0b04d313e763d3338e463"}, - {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:51431f6b2750eb9b9d2b2952d3cc9b15d0215e1b8f37b7a3239744d9b487325d"}, - {file = "pyzmq-26.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdbc78ae2065042de48a65f1421b8af6b76a0386bb487b41955818c3c1ce7bed"}, - {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d14f50d61a89b0925e4d97a0beba6053eb98c426c5815d949a43544f05a0c7ec"}, - {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:004837cb958988c75d8042f5dac19a881f3d9b3b75b2f574055e22573745f841"}, - {file = "pyzmq-26.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b2007f28ce1b8acebdf4812c1aab997a22e57d6a73b5f318b708ef9bcabbe95"}, - {file = "pyzmq-26.2.1-cp38-cp38-win32.whl", hash = "sha256:269c14904da971cb5f013100d1aaedb27c0a246728c341d5d61ddd03f463f2f3"}, - {file = "pyzmq-26.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:31fff709fef3b991cfe7189d2cfe0c413a1d0e82800a182cfa0c2e3668cd450f"}, - {file = "pyzmq-26.2.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a4bffcadfd40660f26d1b3315a6029fd4f8f5bf31a74160b151f5c577b2dc81b"}, - {file = "pyzmq-26.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e76ad4729c2f1cf74b6eb1bdd05f6aba6175999340bd51e6caee49a435a13bf5"}, - {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8b0f5bab40a16e708e78a0c6ee2425d27e1a5d8135c7a203b4e977cee37eb4aa"}, - {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8e47050412f0ad3a9b2287779758073cbf10e460d9f345002d4779e43bb0136"}, - {file = "pyzmq-26.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f18ce33f422d119b13c1363ed4cce245b342b2c5cbbb76753eabf6aa6f69c7d"}, - {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ceb0d78b7ef106708a7e2c2914afe68efffc0051dc6a731b0dbacd8b4aee6d68"}, - {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ebdd96bd637fd426d60e86a29ec14b8c1ab64b8d972f6a020baf08a30d1cf46"}, - {file = "pyzmq-26.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03719e424150c6395b9513f53a5faadcc1ce4b92abdf68987f55900462ac7eec"}, - {file = "pyzmq-26.2.1-cp39-cp39-win32.whl", hash = "sha256:ef5479fac31df4b304e96400fc67ff08231873ee3537544aa08c30f9d22fce38"}, - {file = "pyzmq-26.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:f92a002462154c176dac63a8f1f6582ab56eb394ef4914d65a9417f5d9fde218"}, - {file = "pyzmq-26.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:1fd4b3efc6f62199886440d5e27dd3ccbcb98dfddf330e7396f1ff421bfbb3c2"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53"}, - {file = "pyzmq-26.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ee7152f32c88e0e1b5b17beb9f0e2b14454235795ef68c0c120b6d3d23d12833"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:baa1da72aecf6a490b51fba7a51f1ce298a1e0e86d0daef8265c8f8f9848eb77"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:49135bb327fca159262d8fd14aa1f4a919fe071b04ed08db4c7c37d2f0647162"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bacc1a10c150d58e8a9ee2b2037a70f8d903107e0f0b6e079bf494f2d09c091"}, - {file = "pyzmq-26.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:09dac387ce62d69bec3f06d51610ca1d660e7849eb45f68e38e7f5cf1f49cbcb"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70b3a46ecd9296e725ccafc17d732bfc3cdab850b54bd913f843a0a54dfb2c04"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:59660e15c797a3b7a571c39f8e0b62a1f385f98ae277dfe95ca7eaf05b5a0f12"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0f50db737d688e96ad2a083ad2b453e22865e7e19c7f17d17df416e91ddf67eb"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a003200b6cd64e89b5725ff7e284a93ab24fd54bbac8b4fa46b1ed57be693c27"}, - {file = "pyzmq-26.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f9ba5def063243793dec6603ad1392f735255cbc7202a3a484c14f99ec290705"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1238c2448c58b9c8d6565579393148414a42488a5f916b3f322742e561f6ae0d"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eddb3784aed95d07065bcf94d07e8c04024fdb6b2386f08c197dfe6b3528fda"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0f19c2097fffb1d5b07893d75c9ee693e9cbc809235cf3f2267f0ef6b015f24"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0995fd3530f2e89d6b69a2202e340bbada3191014352af978fa795cb7a446331"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7c6160fe513654e65665332740f63de29ce0d165e053c0c14a161fa60dd0da01"}, - {file = "pyzmq-26.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8ec8e3aea6146b761d6c57fcf8f81fcb19f187afecc19bf1701a48db9617a217"}, - {file = "pyzmq-26.2.1.tar.gz", hash = "sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca"}, + {file = "pyzmq-26.3.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:1586944f4736515af5c6d3a5b150c7e8ca2a2d6e46b23057320584d6f2438f4a"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa7efc695d1fc9f72d91bf9b6c6fe2d7e1b4193836ec530a98faf7d7a7577a58"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd84441e4021cec6e4dd040550386cd9c9ea1d9418ea1a8002dbb7b576026b2b"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9176856f36c34a8aa5c0b35ddf52a5d5cd8abeece57c2cd904cfddae3fd9acd3"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:49334faa749d55b77f084389a80654bf2e68ab5191c0235066f0140c1b670d64"}, + {file = "pyzmq-26.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fd30fc80fe96efb06bea21667c5793bbd65c0dc793187feb39b8f96990680b00"}, + {file = "pyzmq-26.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b2eddfbbfb473a62c3a251bb737a6d58d91907f6e1d95791431ebe556f47d916"}, + {file = "pyzmq-26.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:70b3acb9ad729a53d4e751dace35404a024f188aad406013454216aba5485b4e"}, + {file = "pyzmq-26.3.0-cp310-cp310-win32.whl", hash = "sha256:c1bd75d692cd7c6d862a98013bfdf06702783b75cffbf5dae06d718fecefe8f2"}, + {file = "pyzmq-26.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d7165bcda0dbf203e5ad04d79955d223d84b2263df4db92f525ba370b03a12ab"}, + {file = "pyzmq-26.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:e34a63f71d2ecffb3c643909ad2d488251afeb5ef3635602b3448e609611a7ed"}, + {file = "pyzmq-26.3.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:2833602d9d42c94b9d0d2a44d2b382d3d3a4485be018ba19dddc401a464c617a"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8270d104ec7caa0bdac246d31d48d94472033ceab5ba142881704350b28159c"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c208a977843d18d3bd185f323e4eaa912eb4869cb230947dc6edd8a27a4e558a"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eddc2be28a379c218e0d92e4a432805dcb0ca5870156a90b54c03cd9799f9f8a"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c0b519fa2159c42272f8a244354a0e110d65175647e5185b04008ec00df9f079"}, + {file = "pyzmq-26.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1595533de3a80bf8363372c20bafa963ec4bf9f2b8f539b1d9a5017f430b84c9"}, + {file = "pyzmq-26.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bbef99eb8d18ba9a40f00e8836b8040cdcf0f2fa649684cf7a66339599919d21"}, + {file = "pyzmq-26.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:979486d444ca3c469cd1c7f6a619ce48ff08b3b595d451937db543754bfacb65"}, + {file = "pyzmq-26.3.0-cp311-cp311-win32.whl", hash = "sha256:4b127cfe10b4c56e4285b69fd4b38ea1d368099ea4273d8fb349163fce3cd598"}, + {file = "pyzmq-26.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cf736cc1298ef15280d9fcf7a25c09b05af016656856dc6fe5626fd8912658dd"}, + {file = "pyzmq-26.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2dc46ec09f5d36f606ac8393303149e69d17121beee13c8dac25e2a2078e31c4"}, + {file = "pyzmq-26.3.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:c80653332c6136da7f4d4e143975e74ac0fa14f851f716d90583bc19e8945cea"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e317ee1d4528a03506cb1c282cd9db73660a35b3564096de37de7350e7d87a7"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:943a22ebb3daacb45f76a9bcca9a7b74e7d94608c0c0505da30af900b998ca8d"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fc9e71490d989144981ea21ef4fdfaa7b6aa84aff9632d91c736441ce2f6b00"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e281a8071a06888575a4eb523c4deeefdcd2f5fe4a2d47e02ac8bf3a5b49f695"}, + {file = "pyzmq-26.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:be77efd735bb1064605be8dec6e721141c1421ef0b115ef54e493a64e50e9a52"}, + {file = "pyzmq-26.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a4ac2ffa34f1212dd586af90f4ba894e424f0cabb3a49cdcff944925640f6ac"}, + {file = "pyzmq-26.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ba698c7c252af83b6bba9775035263f0df5f807f0404019916d4b71af8161f66"}, + {file = "pyzmq-26.3.0-cp312-cp312-win32.whl", hash = "sha256:214038aaa88e801e54c2ef0cfdb2e6df27eb05f67b477380a452b595c5ecfa37"}, + {file = "pyzmq-26.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:bad7fe0372e505442482ca3ccbc0d6f38dae81b1650f57a0aa6bbee18e7df495"}, + {file = "pyzmq-26.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:b7b578d604e79e99aa39495becea013fd043fa9f36e4b490efa951f3d847a24d"}, + {file = "pyzmq-26.3.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:fa85953df84beb7b8b73cb3ec3f5d92b62687a09a8e71525c6734e020edf56fd"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:209d09f0ab6ddbcebe64630d1e6ca940687e736f443c265ae15bc4bfad833597"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d35cc1086f1d4f907df85c6cceb2245cb39a04f69c3f375993363216134d76d4"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b380e9087078ba91e45fb18cdd0c25275ffaa045cf63c947be0ddae6186bc9d9"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6d64e74143587efe7c9522bb74d1448128fdf9897cc9b6d8b9927490922fd558"}, + {file = "pyzmq-26.3.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:efba4f53ac7752eea6d8ca38a4ddac579e6e742fba78d1e99c12c95cd2acfc64"}, + {file = "pyzmq-26.3.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:9b0137a1c40da3b7989839f9b78a44de642cdd1ce20dcef341de174c8d04aa53"}, + {file = "pyzmq-26.3.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a995404bd3982c089e57b428c74edd5bfc3b0616b3dbcd6a8e270f1ee2110f36"}, + {file = "pyzmq-26.3.0-cp313-cp313-win32.whl", hash = "sha256:240b1634b9e530ef6a277d95cbca1a6922f44dfddc5f0a3cd6c722a8de867f14"}, + {file = "pyzmq-26.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:fe67291775ea4c2883764ba467eb389c29c308c56b86c1e19e49c9e1ed0cbeca"}, + {file = "pyzmq-26.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:73ca9ae9a9011b714cf7650450cd9c8b61a135180b708904f1f0a05004543dce"}, + {file = "pyzmq-26.3.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:fea7efbd7e49af9d7e5ed6c506dfc7de3d1a628790bd3a35fd0e3c904dc7d464"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4430c7cba23bb0e2ee203eee7851c1654167d956fc6d4b3a87909ccaf3c5825"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:016d89bee8c7d566fad75516b4e53ec7c81018c062d4c51cd061badf9539be52"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04bfe59852d76d56736bfd10ac1d49d421ab8ed11030b4a0332900691507f557"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:1fe05bd0d633a0f672bb28cb8b4743358d196792e1caf04973b7898a0d70b046"}, + {file = "pyzmq-26.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:2aa1a9f236d5b835fb8642f27de95f9edcfd276c4bc1b6ffc84f27c6fb2e2981"}, + {file = "pyzmq-26.3.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:21399b31753bf321043ea60c360ed5052cc7be20739785b1dff1820f819e35b3"}, + {file = "pyzmq-26.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d015efcd96aca8882057e7e6f06224f79eecd22cad193d3e6a0a91ec67590d1f"}, + {file = "pyzmq-26.3.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:18183cc3851b995fdc7e5f03d03b8a4e1b12b0f79dff1ec1da75069af6357a05"}, + {file = "pyzmq-26.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:da87e977f92d930a3683e10ba2b38bcc59adfc25896827e0b9d78b208b7757a6"}, + {file = "pyzmq-26.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf6db401f4957afbf372a4730c6d5b2a234393af723983cbf4bcd13d54c71e1a"}, + {file = "pyzmq-26.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03caa2ffd64252122139d50ec92987f89616b9b92c9ba72920b40e92709d5e26"}, + {file = "pyzmq-26.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fbf206e5329e20937fa19bd41cf3af06d5967f8f7e86b59d783b26b40ced755c"}, + {file = "pyzmq-26.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6fb539a6382a048308b409d8c66d79bf636eda1b24f70c78f2a1fd16e92b037b"}, + {file = "pyzmq-26.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7897b8c8bbbb2bd8cad887bffcb07aede71ef1e45383bd4d6ac049bf0af312a4"}, + {file = "pyzmq-26.3.0-cp38-cp38-win32.whl", hash = "sha256:91dead2daca698ae52ce70ee2adbb94ddd9b5f96877565fd40aa4efd18ecc6a3"}, + {file = "pyzmq-26.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:8c088e009a6d6b9f563336adb906e3a8d3fd64db129acc8d8fd0e9fe22b2dac8"}, + {file = "pyzmq-26.3.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2eaed0d911fb3280981d5495978152fab6afd9fe217fd16f411523665089cef1"}, + {file = "pyzmq-26.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7998b60ef1c105846fb3bfca494769fde3bba6160902e7cd27a8df8257890ee9"}, + {file = "pyzmq-26.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:96c0006a8d1d00e46cb44c8e8d7316d4a232f3d8f2ed43179d4578dbcb0829b6"}, + {file = "pyzmq-26.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e17cc198dc50a25a0f245e6b1e56f692df2acec3ccae82d1f60c34bfb72bbec"}, + {file = "pyzmq-26.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:92a30840f4f2a31f7049d0a7de5fc69dd03b19bd5d8e7fed8d0bde49ce49b589"}, + {file = "pyzmq-26.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f52eba83272a26b444f4b8fc79f2e2c83f91d706d693836c9f7ccb16e6713c31"}, + {file = "pyzmq-26.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:952085a09ff32115794629ba47f8940896d7842afdef1283332109d38222479d"}, + {file = "pyzmq-26.3.0-cp39-cp39-win32.whl", hash = "sha256:0240289e33e3fbae44a5db73e54e955399179332a6b1d47c764a4983ec1524c3"}, + {file = "pyzmq-26.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b2db7c82f08b8ce44c0b9d1153ce63907491972a7581e8b6adea71817f119df8"}, + {file = "pyzmq-26.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:2d3459b6311463c96abcb97808ee0a1abb0d932833edb6aa81c30d622fd4a12d"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad03f4252d9041b0635c37528dfa3f44b39f46024ae28c8567f7423676ee409b"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f3dfb68cf7bf4cfdf34283a75848e077c5defa4907506327282afe92780084d"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:356ec0e39c5a9cda872b65aca1fd8a5d296ffdadf8e2442b70ff32e73ef597b1"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:749d671b0eec8e738bbf0b361168369d8c682b94fcd458c20741dc4d69ef5278"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f950f17ae608e0786298340163cac25a4c5543ef25362dd5ddb6dcb10b547be9"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4fc9903a73c25be9d5fe45c87faababcf3879445efa16140146b08fccfac017"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c15b69af22030960ac63567e98ad8221cddf5d720d9cf03d85021dfd452324ef"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cf9ab0dff4dbaa2e893eb608373c97eb908e53b7d9793ad00ccbd082c0ee12f"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ec332675f6a138db57aad93ae6387953763f85419bdbd18e914cb279ee1c451"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:eb96568a22fe070590942cd4780950e2172e00fb033a8b76e47692583b1bd97c"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:009a38241c76184cb004c869e82a99f0aee32eda412c1eb44df5820324a01d25"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c22a12713707467abedc6d75529dd365180c4c2a1511268972c6e1d472bd63e"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1614fcd116275d24f2346ffca4047a741c546ad9d561cbf7813f11226ca4ed2c"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e2cafe7e9c7fed690e8ecf65af119f9c482923b5075a78f6f7629c63e1b4b1d"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:14e0b81753424bd374075df6cc30b87f2c99e5f022501d97eff66544ca578941"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:21c6ddb98557a77cfe3366af0c5600fb222a1b2de5f90d9cd052b324e0c295e8"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc81d5d60c9d40e692de14b8d884d43cf67562402b931681f0ccb3ce6b19875"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52b064fafef772d0f5dbf52d4c39f092be7bc62d9a602fe6e82082e001326de3"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b72206eb041f780451c61e1e89dbc3705f3d66aaaa14ee320d4f55864b13358a"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab78dc21c7b1e13053086bcf0b4246440b43b5409904b73bfd1156654ece8a1"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0b42403ad7d1194dca9574cd3c56691c345f4601fa2d0a33434f35142baec7ac"}, + {file = "pyzmq-26.3.0.tar.gz", hash = "sha256:f1cd68b8236faab78138a8fc703f7ca0ad431b17a3fcac696358600d4e6243b3"}, ] [package.dependencies] @@ -2718,114 +2703,125 @@ files = [ [[package]] name = "rpds-py" -version = "0.22.3" +version = "0.24.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" files = [ - {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, - {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec"}, - {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00"}, - {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf"}, - {file = "rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652"}, - {file = "rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8"}, - {file = "rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f"}, - {file = "rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1"}, - {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74"}, - {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a"}, - {file = "rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64"}, - {file = "rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c"}, - {file = "rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e"}, - {file = "rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15"}, - {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61"}, - {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7"}, - {file = "rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627"}, - {file = "rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4"}, - {file = "rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84"}, - {file = "rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518"}, - {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16"}, - {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f"}, - {file = "rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de"}, - {file = "rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9"}, - {file = "rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3"}, - {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b"}, - {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333"}, - {file = "rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730"}, - {file = "rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf"}, - {file = "rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea"}, - {file = "rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543"}, - {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831"}, - {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520"}, - {file = "rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9"}, - {file = "rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe"}, - {file = "rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7"}, - {file = "rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6"}, - {file = "rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d"}, + {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, + {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875"}, + {file = "rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07"}, + {file = "rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718"}, + {file = "rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a"}, + {file = "rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56"}, + {file = "rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30"}, + {file = "rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9"}, + {file = "rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143"}, + {file = "rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45"}, + {file = "rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103"}, + {file = "rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f"}, + {file = "rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e"}, ] [[package]] @@ -2905,80 +2901,80 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.37" +version = "2.0.40" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"}, - {file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"}, - {file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"}, - {file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"}, - {file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"}, - {file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"}, - {file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"}, - {file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"}, - {file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"}, - {file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + {file = "SQLAlchemy-2.0.40-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ae9597cab738e7cc823f04a704fb754a9249f0b6695a6aeb63b74055cd417a96"}, + {file = "SQLAlchemy-2.0.40-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a5c21ab099a83d669ebb251fddf8f5cee4d75ea40a5a1653d9c43d60e20867"}, + {file = "SQLAlchemy-2.0.40-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bece9527f5a98466d67fb5d34dc560c4da964240d8b09024bb21c1246545e04e"}, + {file = "SQLAlchemy-2.0.40-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:8bb131ffd2165fae48162c7bbd0d97c84ab961deea9b8bab16366543deeab625"}, + {file = "SQLAlchemy-2.0.40-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9408fd453d5f8990405cc9def9af46bfbe3183e6110401b407c2d073c3388f47"}, + {file = "SQLAlchemy-2.0.40-cp37-cp37m-win32.whl", hash = "sha256:00a494ea6f42a44c326477b5bee4e0fc75f6a80c01570a32b57e89cf0fbef85a"}, + {file = "SQLAlchemy-2.0.40-cp37-cp37m-win_amd64.whl", hash = "sha256:c7b927155112ac858357ccf9d255dd8c044fd9ad2dc6ce4c4149527c901fa4c3"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1ea21bef99c703f44444ad29c2c1b6bd55d202750b6de8e06a955380f4725d7"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:afe63b208153f3a7a2d1a5b9df452b0673082588933e54e7c8aac457cf35e758"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8aae085ea549a1eddbc9298b113cffb75e514eadbb542133dd2b99b5fb3b6af"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ea9181284754d37db15156eb7be09c86e16e50fbe77610e9e7bee09291771a1"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5434223b795be5c5ef8244e5ac98056e290d3a99bdcc539b916e282b160dda00"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15d08d5ef1b779af6a0909b97be6c1fd4298057504eb6461be88bd1696cb438e"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-win32.whl", hash = "sha256:cd2f75598ae70bcfca9117d9e51a3b06fe29edd972fdd7fd57cc97b4dbf3b08a"}, + {file = "sqlalchemy-2.0.40-cp310-cp310-win_amd64.whl", hash = "sha256:2cbafc8d39ff1abdfdda96435f38fab141892dc759a2165947d1a8fffa7ef596"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6bacab7514de6146a1976bc56e1545bee247242fab030b89e5f70336fc0003e"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5654d1ac34e922b6c5711631f2da497d3a7bffd6f9f87ac23b35feea56098011"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35904d63412db21088739510216e9349e335f142ce4a04b69e2528020ee19ed4"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7a80ed86d6aaacb8160a1caef6680d4ddd03c944d985aecee940d168c411d1"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:519624685a51525ddaa7d8ba8265a1540442a2ec71476f0e75241eb8263d6f51"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ee5f9999a5b0e9689bed96e60ee53c3384f1a05c2dd8068cc2e8361b0df5b7a"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-win32.whl", hash = "sha256:c0cae71e20e3c02c52f6b9e9722bca70e4a90a466d59477822739dc31ac18b4b"}, + {file = "sqlalchemy-2.0.40-cp311-cp311-win_amd64.whl", hash = "sha256:574aea2c54d8f1dd1699449f332c7d9b71c339e04ae50163a3eb5ce4c4325ee4"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d3b31d0a1c44b74d3ae27a3de422dfccd2b8f0b75e51ecb2faa2bf65ab1ba0d"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f7a0f506cf78c80450ed1e816978643d3969f99c4ac6b01104a6fe95c5490a"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bb933a650323e476a2e4fbef8997a10d0003d4da996aad3fd7873e962fdde4d"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6959738971b4745eea16f818a2cd086fb35081383b078272c35ece2b07012716"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:110179728e442dae85dd39591beb74072ae4ad55a44eda2acc6ec98ead80d5f2"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8040680eaacdce4d635f12c55c714f3d4c7f57da2bc47a01229d115bd319191"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-win32.whl", hash = "sha256:650490653b110905c10adac69408380688cefc1f536a137d0d69aca1069dc1d1"}, + {file = "sqlalchemy-2.0.40-cp312-cp312-win_amd64.whl", hash = "sha256:2be94d75ee06548d2fc591a3513422b873490efb124048f50556369a834853b0"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500"}, + {file = "sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:50f5885bbed261fc97e2e66c5156244f9704083a674b8d17f24c72217d29baf5"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf0e99cdb600eabcd1d65cdba0d3c91418fee21c4aa1d28db47d095b1064a7d8"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe147fcd85aaed53ce90645c91ed5fca0cc88a797314c70dfd9d35925bd5d106"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf7cee56bd552385c1ee39af360772fbfc2f43be005c78d1140204ad6148438"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4aeb939bcac234b88e2d25d5381655e8353fe06b4e50b1c55ecffe56951d18c2"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c268b5100cfeaa222c40f55e169d484efa1384b44bf9ca415eae6d556f02cb08"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-win32.whl", hash = "sha256:46628ebcec4f23a1584fb52f2abe12ddb00f3bb3b7b337618b80fc1b51177aff"}, + {file = "sqlalchemy-2.0.40-cp38-cp38-win_amd64.whl", hash = "sha256:7e0505719939e52a7b0c65d20e84a6044eb3712bb6f239c6b1db77ba8e173a37"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c884de19528e0fcd9dc34ee94c810581dd6e74aef75437ff17e696c2bfefae3e"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1abb387710283fc5983d8a1209d9696a4eae9db8d7ac94b402981fe2fe2e39ad"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cfa124eda500ba4b0d3afc3e91ea27ed4754e727c7f025f293a22f512bcd4c9"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b6b28d303b9d57c17a5164eb1fd2d5119bb6ff4413d5894e74873280483eeb5"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b5a5bbe29c10c5bfd63893747a1bf6f8049df607638c786252cb9243b86b6706"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f0fda83e113bb0fb27dc003685f32a5dcb99c9c4f41f4fa0838ac35265c23b5c"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-win32.whl", hash = "sha256:957f8d85d5e834397ef78a6109550aeb0d27a53b5032f7a57f2451e1adc37e98"}, + {file = "sqlalchemy-2.0.40-cp39-cp39-win_amd64.whl", hash = "sha256:1ffdf9c91428e59744f8e6f98190516f8e1d05eec90e936eb08b257332c5e870"}, + {file = "sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a"}, + {file = "sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00"}, +] + +[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 (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +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"] @@ -2989,7 +2985,7 @@ mysql-connector = ["mysql-connector-python"] oracle = ["cx_oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] postgresql-pg8000 = ["pg8000 (>=1.29.1)"] postgresql-psycopg = ["psycopg (>=3.0.7)"] postgresql-psycopg2binary = ["psycopg2-binary"] @@ -3019,13 +3015,13 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "syrupy" -version = "4.8.1" +version = "4.9.1" description = "Pytest Snapshot Test Utility" optional = false python-versions = ">=3.8.1" files = [ - {file = "syrupy-4.8.1-py3-none-any.whl", hash = "sha256:274f97cbaf44175f5e478a2f3a53559d31f41c66c6bf28131695f94ac893ea00"}, - {file = "syrupy-4.8.1.tar.gz", hash = "sha256:8da8c0311e6d92de0b15767768c6ab98982b7b4a4c67083c08fbac3fbad4d44c"}, + {file = "syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda"}, + {file = "syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4"}, ] [package.dependencies] @@ -3174,24 +3170,38 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"}, + {file = "typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, + {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, ] +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "tzdata" -version = "2025.1" +version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, - {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] [[package]] @@ -3510,4 +3520,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "a9a2c1b3ebd06e93ff340bed08e0c69886694c51bbf2c07033c170b1b77878bb" +content-hash = "612ce8eb161f2a215258e4798f6adb1d9bc7ed6e9f60d505fbb4aac99a46ec9f" diff --git a/pyproject.toml b/pyproject.toml index cc25b797..56b78a48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,9 @@ langchain-core = ">=0.2.13,<0.4.0" psycopg = "^3" psycopg-pool = "^3.2.1" sqlalchemy = "^2" -pgvector = "<0.4" -numpy = ">=1.21" +pgvector = ">=0.2.5,<0.4" +numpy = "^1.21" +asyncpg = "^0.30.0" [tool.poetry.group.docs.dependencies] diff --git a/tests/unit_tests/fake_embeddings.py b/tests/unit_tests/fake_embeddings.py index 81fd2aa5..376695f8 100644 --- a/tests/unit_tests/fake_embeddings.py +++ b/tests/unit_tests/fake_embeddings.py @@ -1,4 +1,5 @@ """Copied from community.""" + from typing import List from langchain_core.embeddings import Embeddings diff --git a/tests/unit_tests/fixtures/filtering_test_cases.py b/tests/unit_tests/fixtures/filtering_test_cases.py index 181e8ba1..28ffca3c 100644 --- a/tests/unit_tests/fixtures/filtering_test_cases.py +++ b/tests/unit_tests/fixtures/filtering_test_cases.py @@ -1,4 +1,5 @@ """Module needs to move to a stasndalone package.""" + from langchain_core.documents import Document metadatas = [ diff --git a/tests/unit_tests/fixtures/metadata_filtering_data.py b/tests/unit_tests/fixtures/metadata_filtering_data.py new file mode 100644 index 00000000..446263d5 --- /dev/null +++ b/tests/unit_tests/fixtures/metadata_filtering_data.py @@ -0,0 +1,249 @@ +METADATAS = [ + { + "name": "Wireless Headphones", + "code": "WH001", + "price": 149.99, + "is_available": True, + "release_date": "2023-10-26", + "tags": ["audio", "wireless", "electronics"], + "dimensions": [18.5, 7.2, 21.0], + "inventory_location": [101, 102], + "available_quantity": 50, + }, + { + "name": "Ergonomic Office Chair", + "code": "EC002", + "price": 299.00, + "is_available": True, + "release_date": "2023-08-15", + "tags": ["furniture", "office", "ergonomic"], + "dimensions": [65.0, 60.0, 110.0], + "inventory_location": [201], + "available_quantity": 10, + }, + { + "name": "Stainless Steel Water Bottle", + "code": "WB003", + "price": 25.50, + "is_available": False, + "release_date": "2024-01-05", + "tags": ["hydration", "eco-friendly", "kitchen"], + "dimensions": [7.5, 7.5, 25.0], + "available_quantity": 0, + }, + { + "name": "Smart Fitness Tracker", + "code": "FT004", + "price": 79.95, + "is_available": True, + "release_date": "2023-11-12", + "tags": ["fitness", "wearable", "technology"], + "dimensions": [2.0, 1.0, 25.0], + "inventory_location": [401], + "available_quantity": 100, + }, +] + +FILTERING_TEST_CASES = [ + # These tests only involve equality checks + ( + {"code": "FT004"}, + ["FT004"], + ), + # String field + ( + # check name + {"name": "Smart Fitness Tracker"}, + ["FT004"], + ), + # Boolean fields + ( + {"is_available": True}, + ["WH001", "FT004", "EC002"], + ), + # And semantics for top level filtering + ( + {"code": "WH001", "is_available": True}, + ["WH001"], + ), + # These involve equality checks and other operators + # like $ne, $gt, $gte, $lt, $lte + ( + {"available_quantity": {"$eq": 10}}, + ["EC002"], + ), + ( + {"available_quantity": {"$ne": 0}}, + ["WH001", "FT004", "EC002"], + ), + ( + {"available_quantity": {"$gt": 60}}, + ["FT004"], + ), + ( + {"available_quantity": {"$gte": 50}}, + ["WH001", "FT004"], + ), + ( + {"available_quantity": {"$lt": 5}}, + ["WB003"], + ), + ( + {"available_quantity": {"$lte": 10}}, + ["WB003", "EC002"], + ), + # Repeat all the same tests with name (string column) + ( + {"code": {"$eq": "WH001"}}, + ["WH001"], + ), + ( + {"code": {"$ne": "WB003"}}, + ["WH001", "FT004", "EC002"], + ), + # And also gt, gte, lt, lte relying on lexicographical ordering + ( + {"name": {"$gt": "Wireless Headphones"}}, + [], + ), + ( + {"name": {"$gte": "Wireless Headphones"}}, + ["WH001"], + ), + ( + {"name": {"$lt": "Smart Fitness Tracker"}}, + ["EC002"], + ), + ( + {"name": {"$lte": "Smart Fitness Tracker"}}, + ["FT004", "EC002"], + ), + ( + {"is_available": {"$eq": True}}, + ["WH001", "FT004", "EC002"], + ), + ( + {"is_available": {"$ne": True}}, + ["WB003"], + ), + # Test float column. + ( + {"price": {"$gt": 200.0}}, + ["EC002"], + ), + ( + {"price": {"$gte": 149.99}}, + ["WH001", "EC002"], + ), + ( + {"price": {"$lt": 50.0}}, + ["WB003"], + ), + ( + {"price": {"$lte": 79.95}}, + ["FT004", "WB003"], + ), + # These involve usage of AND, OR and NOT operators + ( + {"$or": [{"code": "WH001"}, {"code": "EC002"}]}, + ["WH001", "EC002"], + ), + ( + {"$or": [{"code": "WH001"}, {"available_quantity": 10}]}, + ["WH001", "EC002"], + ), + ( + {"$and": [{"code": "WH001"}, {"code": "EC002"}]}, + [], + ), + # Test for $not operator + ( + {"$not": {"code": "WB003"}}, + ["WH001", "FT004", "EC002"], + ), + ( + {"$not": [{"code": "WB003"}]}, + ["WH001", "FT004", "EC002"], + ), + ( + {"$not": {"available_quantity": 0}}, + ["WH001", "FT004", "EC002"], + ), + ( + {"$not": [{"available_quantity": 0}]}, + ["WH001", "FT004", "EC002"], + ), + ( + {"$not": {"is_available": True}}, + ["WB003"], + ), + ( + {"$not": [{"is_available": True}]}, + ["WB003"], + ), + ( + {"$not": {"price": {"$gt": 150.0}}}, + ["WH001", "FT004", "WB003"], + ), + ( + {"$not": [{"price": {"$gt": 150.0}}]}, + ["WH001", "FT004", "WB003"], + ), + # These involve special operators like $in, $nin, $between + # Test between + ( + {"available_quantity": {"$between": (40, 60)}}, + ["WH001"], + ), + # Test in + ( + {"name": {"$in": ["Smart Fitness Tracker", "Stainless Steel Water Bottle"]}}, + ["FT004", "WB003"], + ), + # With numeric fields + ( + {"available_quantity": {"$in": [0, 10]}}, + ["WB003", "EC002"], + ), + # Test nin + ( + {"name": {"$nin": ["Smart Fitness Tracker", "Stainless Steel Water Bottle"]}}, + ["WH001", "EC002"], + ), + ## with numeric fields + ( + {"available_quantity": {"$nin": [50, 0, 10]}}, + ["FT004"], + ), + # These involve special operators like $like, $ilike that + # may be specified to certain databases. + ( + {"name": {"$like": "Wireless%"}}, + ["WH001"], + ), + ( + {"name": {"$like": "%less%"}}, # adam and jane + ["WH001", "WB003"], + ), + # These involve the special operator $exists + ( + {"tags": {"$exists": False}}, + [], + ), + ( + {"inventory_location": {"$exists": False}}, + ["WB003"], + ), +] + +NEGATIVE_TEST_CASES = [ + {"$nor": [{"code": "WH001"}, {"code": "EC002"}]}, + {"$and": {"is_available": True}}, + {"is_available": {"$and": True}}, + {"is_available": {"name": "{Wireless Headphones", "code": "EC002"}}, + {"my column": {"$and": True}}, + {"is_available": {"code": "WH001", "code": "EC002"}}, + {"$and": {}}, + {"$and": []}, + {"$not": True}, +] diff --git a/tests/unit_tests/test_imports.py b/tests/unit_tests/test_imports.py index 4513d181..445a4b1e 100644 --- a/tests/unit_tests/test_imports.py +++ b/tests/unit_tests/test_imports.py @@ -2,7 +2,11 @@ EXPECTED_ALL = [ "__version__", + "Column", + "ColumnDict", + "PGEngine", "PGVector", + "PGVectorStore", "PGVectorTranslator", "PostgresChatMessageHistory", ] diff --git a/tests/unit_tests/v1/__init__.py b/tests/unit_tests/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/test_chat_histories.py b/tests/unit_tests/v1/test_chat_histories.py similarity index 100% rename from tests/unit_tests/test_chat_histories.py rename to tests/unit_tests/v1/test_chat_histories.py diff --git a/tests/unit_tests/test_vectorstore.py b/tests/unit_tests/v1/test_vectorstore.py similarity index 99% rename from tests/unit_tests/test_vectorstore.py rename to tests/unit_tests/v1/test_vectorstore.py index 2383daf3..a4f0f9f6 100644 --- a/tests/unit_tests/test_vectorstore.py +++ b/tests/unit_tests/v1/test_vectorstore.py @@ -1,4 +1,5 @@ """Test PGVector functionality.""" + import contextlib from typing import Any, AsyncGenerator, Dict, Generator, List, Optional, Sequence diff --git a/tests/unit_tests/test_vectorstore_standard_tests.py b/tests/unit_tests/v1/test_vectorstore_standard_tests.py similarity index 93% rename from tests/unit_tests/test_vectorstore_standard_tests.py rename to tests/unit_tests/v1/test_vectorstore_standard_tests.py index a8834f7a..cbf42b94 100644 --- a/tests/unit_tests/test_vectorstore_standard_tests.py +++ b/tests/unit_tests/v1/test_vectorstore_standard_tests.py @@ -4,7 +4,7 @@ from langchain_core.vectorstores import VectorStore from langchain_tests.integration_tests import VectorStoreIntegrationTests -from tests.unit_tests.test_vectorstore import aget_vectorstore, get_vectorstore +from tests.unit_tests.v1.test_vectorstore import aget_vectorstore, get_vectorstore class TestSync(VectorStoreIntegrationTests): diff --git a/tests/unit_tests/v2/__init__.py b/tests/unit_tests/v2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit_tests/v2/test_async_pg_vectorstore.py b/tests/unit_tests/v2/test_async_pg_vectorstore.py new file mode 100644 index 00000000..bcf37e8b --- /dev/null +++ b/tests/unit_tests/v2/test_async_pg_vectorstore.py @@ -0,0 +1,338 @@ +import uuid +from typing import AsyncIterator, Sequence + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text +from sqlalchemy.engine.row import RowMapping + +from langchain_postgres import Column, PGEngine +from langchain_postgres.v2.async_vectorstore import AsyncPGVectorStore +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "default" + str(uuid.uuid4()) +DEFAULT_TABLE_SYNC = "default_sync" + str(uuid.uuid4()) +CUSTOM_TABLE = "custom" + str(uuid.uuid4()) +VECTOR_SIZE = 768 + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +texts = ["foo", "bar", "baz"] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query(texts[i]) for i in range(len(texts))] + + +async def aexecute(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + +async def afetch(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + result_fetch = result_map.fetchall() + return result_fetch + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestVectorStore: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + + yield engine + await aexecute(engine, f'DROP TABLE IF EXISTS "{DEFAULT_TABLE}"') + await aexecute(engine, f'DROP TABLE IF EXISTS "{CUSTOM_TABLE}"') + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[AsyncPGVectorStore]: + await engine._ainit_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + ) + yield vs + + @pytest_asyncio.fixture(scope="class") + async def vs_custom(self, engine: PGEngine) -> AsyncIterator[AsyncPGVectorStore]: + await engine._ainit_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + metadata_json_column="mymeta", + ) + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + yield vs + + async def test_init_with_constructor(self, engine: PGEngine) -> None: + with pytest.raises(Exception): + AsyncPGVectorStore( + key={}, + engine=engine._pool, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="noname", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + + async def test_post_init(self, engine: PGEngine) -> None: + with pytest.raises(ValueError): + await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="noname", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + + async def test_aadd_texts(self, engine: PGEngine, vs: AsyncPGVectorStore) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, metadatas, ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 6 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_texts_edge_cases( + self, engine: PGEngine, vs: AsyncPGVectorStore + ) -> None: + texts = ["Taylor's", '"Swift"', "best-friend"] + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_docs(self, engine: PGEngine, vs: AsyncPGVectorStore) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_documents(docs, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_docs_no_ids( + self, engine: PGEngine, vs: AsyncPGVectorStore + ) -> None: + await vs.aadd_documents(docs) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_adelete(self, engine: PGEngine, vs: AsyncPGVectorStore) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + # delete an ID + await vs.adelete([ids[0]]) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 2 + # delete with no ids + result = await vs.adelete() + assert result == False + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + ##### Custom Vector Store ##### + async def test_aadd_embeddings( + self, engine: PGEngine, vs_custom: AsyncPGVectorStore + ) -> None: + await vs_custom.aadd_embeddings( + texts=texts, embeddings=embeddings, metadatas=metadatas + ) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_aadd_texts_custom( + self, engine: PGEngine, vs_custom: AsyncPGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_custom.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] is None + assert results[0]["source"] is None + + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_custom.aadd_texts(texts, metadatas, ids) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 6 + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_aadd_docs_custom( + self, engine: PGEngine, vs_custom: AsyncPGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + docs = [ + Document( + page_content=texts[i], + metadata={"page": str(i), "source": "postgres"}, + ) + for i in range(len(texts)) + ] + await vs_custom.aadd_documents(docs, ids=ids) + + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_adelete_custom( + self, engine: PGEngine, vs_custom: AsyncPGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_custom.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + content = [result["mycontent"] for result in results] + assert len(results) == 3 + assert "foo" in content + # delete an ID + await vs_custom.adelete([ids[0]]) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + content = [result["mycontent"] for result in results] + assert len(results) == 2 + assert "foo" not in content + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_ignore_metadata_columns(self, engine: PGEngine) -> None: + column_to_ignore = "source" + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + ignore_metadata_columns=[column_to_ignore], + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_json_column="mymeta", + ) + assert column_to_ignore not in vs.metadata_columns + + async def test_create_vectorstore_with_invalid_parameters_1( + self, engine: PGEngine + ) -> None: + with pytest.raises(ValueError): + await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["random_column"], # invalid metadata column + ) + + async def test_create_vectorstore_with_invalid_parameters_2( + self, engine: PGEngine + ) -> None: + with pytest.raises(ValueError): + await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="langchain_id", # invalid content column type + embedding_column="myembedding", + metadata_columns=["random_column"], + ) + + async def test_create_vectorstore_with_invalid_parameters_3( + self, engine: PGEngine + ) -> None: + with pytest.raises(ValueError): + await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="random_column", # invalid embedding column + metadata_columns=["random_column"], + ) + + async def test_create_vectorstore_with_invalid_parameters_4( + self, engine: PGEngine + ) -> None: + with pytest.raises(ValueError): + await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="langchain_id", # invalid embedding column data type + metadata_columns=["random_column"], + ) + + async def test_create_vectorstore_with_invalid_parameters_5( + self, engine: PGEngine + ) -> None: + with pytest.raises(ValueError): + await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="langchain_id", + metadata_columns=["random_column"], + ignore_metadata_columns=[ + "one", + "two", + ], # invalid use of metadata_columns and ignore columns + ) + + async def test_create_vectorstore_with_init(self, engine: PGEngine) -> None: + with pytest.raises(Exception): + AsyncPGVectorStore( + key={}, + engine=engine._pool, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["random_column"], # invalid metadata column + ) diff --git a/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py b/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py new file mode 100644 index 00000000..85bb6a65 --- /dev/null +++ b/tests/unit_tests/v2/test_async_pg_vectorstore_from_methods.py @@ -0,0 +1,187 @@ +import os +import uuid +from typing import AsyncIterator, Sequence + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text +from sqlalchemy.engine.row import RowMapping + +from langchain_postgres import Column, PGEngine +from langchain_postgres.v2.async_vectorstore import AsyncPGVectorStore +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "default" + str(uuid.uuid4()).replace("-", "_") +DEFAULT_TABLE_SYNC = "default_sync" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE = "custom" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE_WITH_INT_ID = "custom_sync" + str(uuid.uuid4()).replace("-", "_") +VECTOR_SIZE = 768 + + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +texts = ["foo", "bar", "baz"] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query(texts[i]) for i in range(len(texts))] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + +async def afetch(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + result_fetch = result_map.fetchall() + return result_fetch + + +@pytest.mark.enable_socket +@pytest.mark.asyncio +class TestVectorStoreFromMethods: + @pytest_asyncio.fixture + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + await engine._ainit_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + await engine._ainit_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=False, + ) + await engine._ainit_vectorstore_table( + CUSTOM_TABLE_WITH_INT_ID, + VECTOR_SIZE, + id_column=Column(name="integer_id", data_type="INTEGER", nullable=False), + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=False, + ) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE_WITH_INT_ID}") + await engine.close() + + async def test_afrom_texts(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await AsyncPGVectorStore.afrom_texts( + texts, + embeddings_service, + engine, + DEFAULT_TABLE, + metadatas=metadatas, + ids=ids, + ) + results = await afetch(engine, f"SELECT * FROM {DEFAULT_TABLE}") + assert len(results) == 3 + await aexecute(engine, f"TRUNCATE TABLE {DEFAULT_TABLE}") + + async def test_afrom_docs(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await AsyncPGVectorStore.afrom_documents( + docs, + embeddings_service, + engine, + DEFAULT_TABLE, + ids=ids, + ) + results = await afetch(engine, f"SELECT * FROM {DEFAULT_TABLE}") + assert len(results) == 3 + await aexecute(engine, f"TRUNCATE TABLE {DEFAULT_TABLE}") + + async def test_afrom_texts_custom(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await AsyncPGVectorStore.afrom_texts( + texts, + embeddings_service, + engine, + CUSTOM_TABLE, + ids=ids, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + results = await afetch(engine, f"SELECT * FROM {CUSTOM_TABLE}") + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] is None + assert results[0]["source"] is None + + async def test_afrom_docs_custom(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + docs = [ + Document( + page_content=texts[i], + metadata={"page": str(i), "source": "postgres"}, + ) + for i in range(len(texts)) + ] + await AsyncPGVectorStore.afrom_documents( + docs, + embeddings_service, + engine, + CUSTOM_TABLE, + ids=ids, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + + results = await afetch(engine, f"SELECT * FROM {CUSTOM_TABLE}") + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine, f"TRUNCATE TABLE {CUSTOM_TABLE}") + + async def test_afrom_docs_custom_with_int_id(self, engine: PGEngine) -> None: + ids = [i for i in range(len(texts))] + docs = [ + Document( + page_content=texts[i], + metadata={"page": str(i), "source": "postgres"}, + ) + for i in range(len(texts)) + ] + await AsyncPGVectorStore.afrom_documents( + docs, + embeddings_service, + engine, + CUSTOM_TABLE_WITH_INT_ID, + ids=ids, + id_column="integer_id", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + + results = await afetch(engine, f"SELECT * FROM {CUSTOM_TABLE_WITH_INT_ID}") + assert len(results) == 3 + for row in results: + assert isinstance(row["integer_id"], int) + await aexecute(engine, f"TRUNCATE TABLE {CUSTOM_TABLE_WITH_INT_ID}") diff --git a/tests/unit_tests/v2/test_async_pg_vectorstore_index.py b/tests/unit_tests/v2/test_async_pg_vectorstore_index.py new file mode 100644 index 00000000..094b66a5 --- /dev/null +++ b/tests/unit_tests/v2/test_async_pg_vectorstore_index.py @@ -0,0 +1,127 @@ +import os +import uuid +from typing import AsyncIterator + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text + +from langchain_postgres import PGEngine +from langchain_postgres.v2.async_vectorstore import AsyncPGVectorStore +from langchain_postgres.v2.indexes import ( + DEFAULT_INDEX_NAME_SUFFIX, + DistanceStrategy, + HNSWIndex, + IVFFlatIndex, +) +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +uuid_str = str(uuid.uuid4()).replace("-", "_") +DEFAULT_TABLE = "default" + uuid_str +DEFAULT_INDEX_NAME = "index" + uuid_str +VECTOR_SIZE = 768 +SIMPLE_TABLE = "default_table" + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +texts = ["foo", "bar", "baz"] +ids = [str(uuid.uuid4()) for i in range(len(texts))] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query("foo") for i in range(len(texts))] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestIndex: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {SIMPLE_TABLE}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[AsyncPGVectorStore]: + await engine._ainit_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + ) + + await vs.aadd_texts(texts, ids=ids) + await vs.adrop_vector_index(DEFAULT_INDEX_NAME) + yield vs + + async def test_apply_default_name_vector_index(self, engine: PGEngine) -> None: + await engine._ainit_vectorstore_table(SIMPLE_TABLE, VECTOR_SIZE) + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=SIMPLE_TABLE, + ) + await vs.aadd_texts(texts, ids=ids) + await vs.adrop_vector_index() + index = HNSWIndex() + await vs.aapply_vector_index(index) + assert await vs.is_valid_index() + await vs.adrop_vector_index() + + async def test_aapply_vector_index(self, vs: AsyncPGVectorStore) -> None: + index = HNSWIndex(name=DEFAULT_INDEX_NAME) + await vs.aapply_vector_index(index) + assert await vs.is_valid_index(DEFAULT_INDEX_NAME) + await vs.adrop_vector_index(DEFAULT_INDEX_NAME) + + async def test_areindex(self, vs: AsyncPGVectorStore) -> None: + if not await vs.is_valid_index(DEFAULT_INDEX_NAME): + index = HNSWIndex(name=DEFAULT_INDEX_NAME) + await vs.aapply_vector_index(index) + await vs.areindex(DEFAULT_INDEX_NAME) + await vs.areindex(DEFAULT_INDEX_NAME) + assert await vs.is_valid_index(DEFAULT_INDEX_NAME) + await vs.adrop_vector_index(DEFAULT_INDEX_NAME) + + async def test_dropindex(self, vs: AsyncPGVectorStore) -> None: + await vs.adrop_vector_index(DEFAULT_INDEX_NAME) + result = await vs.is_valid_index(DEFAULT_INDEX_NAME) + assert not result + + async def test_aapply_vector_index_ivfflat(self, vs: AsyncPGVectorStore) -> None: + index = IVFFlatIndex( + name=DEFAULT_INDEX_NAME, distance_strategy=DistanceStrategy.EUCLIDEAN + ) + await vs.aapply_vector_index(index, concurrently=True) + assert await vs.is_valid_index(DEFAULT_INDEX_NAME) + index = IVFFlatIndex( + name="secondindex", + distance_strategy=DistanceStrategy.INNER_PRODUCT, + ) + await vs.aapply_vector_index(index) + assert await vs.is_valid_index("secondindex") + await vs.adrop_vector_index("secondindex") + await vs.adrop_vector_index(DEFAULT_INDEX_NAME) + + async def test_is_valid_index(self, vs: AsyncPGVectorStore) -> None: + is_valid = await vs.is_valid_index("invalid_index") + assert is_valid == False diff --git a/tests/unit_tests/v2/test_async_pg_vectorstore_search.py b/tests/unit_tests/v2/test_async_pg_vectorstore_search.py new file mode 100644 index 00000000..dc3a771b --- /dev/null +++ b/tests/unit_tests/v2/test_async_pg_vectorstore_search.py @@ -0,0 +1,339 @@ +import os +import uuid +from typing import AsyncIterator + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text + +from langchain_postgres import Column, PGEngine +from langchain_postgres.v2.async_vectorstore import AsyncPGVectorStore +from langchain_postgres.v2.indexes import DistanceStrategy, HNSWQueryOptions +from tests.unit_tests.fixtures.metadata_filtering_data import ( + FILTERING_TEST_CASES, + METADATAS, +) +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "default" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE = "custom" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_FILTER_TABLE = "custom_filter" + str(uuid.uuid4()).replace("-", "_") +VECTOR_SIZE = 768 +sync_method_exception_str = "Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead." + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +# Note: The following texts are chosen to produce diverse +# similarity scores when using the DeterministicFakeEmbedding service. This ensures +# that the test cases can effectively validate the filtering and scoring logic. +# The scoring might be different if using a different embedding service. +texts = ["foo", "bar", "baz", "boo"] +ids = [str(uuid.uuid4()) for i in range(len(texts))] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query("foo") for i in range(len(texts))] + +filter_docs = [ + Document(page_content=texts[i], metadata=METADATAS[i]) for i in range(len(texts)) +] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestVectorStoreSearch: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_FILTER_TABLE}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[AsyncPGVectorStore]: + await engine._ainit_vectorstore_table( + DEFAULT_TABLE, VECTOR_SIZE, store_metadata=False + ) + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + ) + await vs.aadd_documents(docs, ids=ids) + yield vs + + @pytest_asyncio.fixture(scope="class") + async def vs_custom(self, engine: PGEngine) -> AsyncIterator[AsyncPGVectorStore]: + await engine._ainit_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[ + Column("page", "TEXT"), + Column("source", "TEXT"), + ], + store_metadata=False, + ) + + vs_custom = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + index_query_options=HNSWQueryOptions(ef_search=1), + ) + await vs_custom.aadd_documents(docs, ids=ids) + yield vs_custom + + @pytest_asyncio.fixture(scope="class") + async def vs_custom_filter( + self, engine: PGEngine + ) -> AsyncIterator[AsyncPGVectorStore]: + await engine._ainit_vectorstore_table( + CUSTOM_FILTER_TABLE, + VECTOR_SIZE, + metadata_columns=[ + Column("name", "TEXT"), + Column("code", "TEXT"), + Column("price", "FLOAT"), + Column("is_available", "BOOLEAN"), + Column("tags", "TEXT[]"), + Column("inventory_location", "INTEGER[]"), + Column("available_quantity", "INTEGER", nullable=True), + ], + id_column="langchain_id", + store_metadata=False, + ) + + vs_custom_filter = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_FILTER_TABLE, + metadata_columns=[ + "name", + "code", + "price", + "is_available", + "tags", + "inventory_location", + "available_quantity", + ], + id_column="langchain_id", + ) + await vs_custom_filter.aadd_documents(filter_docs, ids=ids) + yield vs_custom_filter + + async def test_asimilarity_search(self, vs: AsyncPGVectorStore) -> None: + results = await vs.asimilarity_search("foo", k=1) + assert len(results) == 1 + assert results == [Document(page_content="foo", id=ids[0])] + results = await vs.asimilarity_search("foo", k=1, filter="content = 'bar'") + assert results == [Document(page_content="bar", id=ids[1])] + + async def test_asimilarity_search_score(self, vs: AsyncPGVectorStore) -> None: + results = await vs.asimilarity_search_with_score("foo") + assert len(results) == 4 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + assert results[0][1] == 0 + + async def test_asimilarity_search_by_vector(self, vs: AsyncPGVectorStore) -> None: + embedding = embeddings_service.embed_query("foo") + results = await vs.asimilarity_search_by_vector(embedding) + assert len(results) == 4 + assert results[0] == Document(page_content="foo", id=ids[0]) + result = await vs.asimilarity_search_with_score_by_vector(embedding=embedding) + assert result[0][0] == Document(page_content="foo", id=ids[0]) + assert result[0][1] == 0 + + async def test_similarity_search_with_relevance_scores_threshold_cosine( + self, vs: AsyncPGVectorStore + ) -> None: + score_threshold = {"score_threshold": 0} + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + # Note: Since tests use FakeEmbeddings which are non-normalized vectors, results might have scores beyond the range [0,1]. + # For a normalized embedding service, a threshold of zero will yield all matched documents. + assert len(results) == 2 + + score_threshold = {"score_threshold": 0.02} # type: ignore + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + assert len(results) == 2 + + score_threshold = {"score_threshold": 0.9} # type: ignore + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + assert len(results) == 1 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + + score_threshold = {"score_threshold": 0.02} # type: ignore + vs.distance_strategy = DistanceStrategy.EUCLIDEAN + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + assert len(results) == 1 + + async def test_similarity_search_with_relevance_scores_threshold_euclidean( + self, engine: PGEngine + ) -> None: + vs = await AsyncPGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + distance_strategy=DistanceStrategy.EUCLIDEAN, + ) + + score_threshold = {"score_threshold": 0.9} + results = await vs.asimilarity_search_with_relevance_scores( + "foo", + **score_threshold, # type: ignore + ) + assert len(results) == 1 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + + async def test_amax_marginal_relevance_search(self, vs: AsyncPGVectorStore) -> None: + results = await vs.amax_marginal_relevance_search("bar") + assert results[0] == Document(page_content="bar", id=ids[1]) + results = await vs.amax_marginal_relevance_search( + "bar", filter="content = 'boo'" + ) + assert results[0] == Document(page_content="boo", id=ids[3]) + + async def test_amax_marginal_relevance_search_vector( + self, vs: AsyncPGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = await vs.amax_marginal_relevance_search_by_vector(embedding) + assert results[0] == Document(page_content="bar", id=ids[1]) + + async def test_amax_marginal_relevance_search_vector_score( + self, vs: AsyncPGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = await vs.amax_marginal_relevance_search_with_score_by_vector( + embedding + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + results = await vs.amax_marginal_relevance_search_with_score_by_vector( + embedding, lambda_mult=0.75, fetch_k=10 + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + async def test_similarity_search(self, vs_custom: AsyncPGVectorStore) -> None: + results = await vs_custom.asimilarity_search("foo", k=1) + assert len(results) == 1 + assert results == [Document(page_content="foo", id=ids[0])] + results = await vs_custom.asimilarity_search( + "foo", k=1, filter="mycontent = 'bar'" + ) + assert results == [Document(page_content="bar", id=ids[1])] + + async def test_similarity_search_score(self, vs_custom: AsyncPGVectorStore) -> None: + results = await vs_custom.asimilarity_search_with_score("foo") + assert len(results) == 4 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + assert results[0][1] == 0 + + async def test_similarity_search_by_vector( + self, vs_custom: AsyncPGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("foo") + results = await vs_custom.asimilarity_search_by_vector(embedding) + assert len(results) == 4 + assert results[0] == Document(page_content="foo", id=ids[0]) + result = await vs_custom.asimilarity_search_with_score_by_vector( + embedding=embedding + ) + assert result[0][0] == Document(page_content="foo", id=ids[0]) + assert result[0][1] == 0 + + async def test_max_marginal_relevance_search( + self, vs_custom: AsyncPGVectorStore + ) -> None: + results = await vs_custom.amax_marginal_relevance_search("bar") + assert results[0] == Document(page_content="bar", id=ids[1]) + results = await vs_custom.amax_marginal_relevance_search( + "bar", filter="mycontent = 'boo'" + ) + assert results[0] == Document(page_content="boo", id=ids[3]) + + async def test_max_marginal_relevance_search_vector( + self, vs_custom: AsyncPGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = await vs_custom.amax_marginal_relevance_search_by_vector(embedding) + assert results[0] == Document(page_content="bar", id=ids[1]) + + async def test_max_marginal_relevance_search_vector_score( + self, vs_custom: AsyncPGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = await vs_custom.amax_marginal_relevance_search_with_score_by_vector( + embedding + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + results = await vs_custom.amax_marginal_relevance_search_with_score_by_vector( + embedding, lambda_mult=0.75, fetch_k=10 + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + async def test_aget_by_ids(self, vs: AsyncPGVectorStore) -> None: + test_ids = [ids[0]] + results = await vs.aget_by_ids(ids=test_ids) + + assert results[0] == Document(page_content="foo", id=ids[0]) + + async def test_aget_by_ids_custom_vs(self, vs_custom: AsyncPGVectorStore) -> None: + test_ids = [ids[0]] + results = await vs_custom.aget_by_ids(ids=test_ids) + + assert results[0] == Document(page_content="foo", id=ids[0]) + + def test_get_by_ids(self, vs: AsyncPGVectorStore) -> None: + test_ids = [ids[0]] + with pytest.raises(Exception, match=sync_method_exception_str): + vs.get_by_ids(ids=test_ids) + + @pytest.mark.parametrize("test_filter, expected_ids", FILTERING_TEST_CASES) + async def test_vectorstore_with_metadata_filters( + self, + vs_custom_filter: AsyncPGVectorStore, + test_filter: dict, + expected_ids: list[str], + ) -> None: + """Test end to end construction and search.""" + docs = await vs_custom_filter.asimilarity_search( + "meow", k=5, filter=test_filter + ) + assert [doc.metadata["code"] for doc in docs] == expected_ids, test_filter diff --git a/tests/unit_tests/v2/test_engine.py b/tests/unit_tests/v2/test_engine.py new file mode 100644 index 00000000..98d65153 --- /dev/null +++ b/tests/unit_tests/v2/test_engine.py @@ -0,0 +1,358 @@ +import os +import uuid +from typing import AsyncIterator, Sequence + +import asyncpg # type: ignore +import pytest +import pytest_asyncio +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import VARCHAR, text +from sqlalchemy.engine.row import RowMapping +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.pool import NullPool + +from langchain_postgres import Column, PGEngine, ColumnDict +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "default" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE = "custom" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TYPEDDICT_TABLE = "custom_td" + str(uuid.uuid4()).replace("-", "_") +INT_ID_CUSTOM_TABLE = "custom_int_id" + str(uuid.uuid4()).replace("-", "_") +DEFAULT_TABLE_SYNC = "default_sync" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE_SYNC = "custom_sync" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TYPEDDICT_TABLE_SYNC = "custom_td_sync" + str(uuid.uuid4()).replace("-", "_") +INT_ID_CUSTOM_TABLE_SYNC = "custom_int_id_sync" + str(uuid.uuid4()).replace("-", "_") +VECTOR_SIZE = 768 + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async def run(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + await engine._run_as_async(run(engine, query)) + + +async def afetch(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async def run(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + result_fetch = result_map.fetchall() + return result_fetch + + return await engine._run_as_async(run(engine, query)) + + +@pytest.mark.enable_socket +@pytest.mark.asyncio +class TestEngineAsync: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + kwargs = { + "pool_size": 3, + "max_overflow": 2, + } + engine = PGEngine.from_connection_string(url=CONNECTION_STRING, **kwargs) + + yield engine + await aexecute(engine, f'DROP TABLE "{CUSTOM_TABLE}"') + await aexecute(engine, f'DROP TABLE "{CUSTOM_TYPEDDICT_TABLE}"') + await aexecute(engine, f'DROP TABLE "{DEFAULT_TABLE}"') + await aexecute(engine, f'DROP TABLE "{INT_ID_CUSTOM_TABLE}"') + await engine.close() + + async def test_init_table(self, engine: PGEngine) -> None: + await engine.ainit_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + id = str(uuid.uuid4()) + content = "coffee" + embedding = await embeddings_service.aembed_query(content) + # Note: DeterministicFakeEmbedding generates a numpy array, converting to list a list of float values + embedding_string = [float(dimension) for dimension in embedding] + stmt = f"INSERT INTO {DEFAULT_TABLE} (langchain_id, content, embedding) VALUES ('{id}', '{content}','{embedding_string}');" + await aexecute(engine, stmt) + + async def test_engine_args(self, engine: PGEngine) -> None: + assert "Pool size: 3" in engine._pool.pool.status() + + async def test_init_table_custom(self, engine: PGEngine) -> None: + await engine.ainit_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="uuid", + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=True, + ) + stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{CUSTOM_TABLE}';" + results = await afetch(engine, stmt) + expected = [ + {"column_name": "uuid", "data_type": "uuid"}, + {"column_name": "my_embedding", "data_type": "USER-DEFINED"}, + {"column_name": "langchain_metadata", "data_type": "json"}, + {"column_name": "my-content", "data_type": "text"}, + {"column_name": "page", "data_type": "text"}, + {"column_name": "source", "data_type": "text"}, + ] + for row in results: + assert row in expected + + async def test_invalid_typed_dict(self, engine: PGEngine) -> None: + with pytest.raises(TypeError): + await engine.ainit_vectorstore_table( + CUSTOM_TYPEDDICT_TABLE, + VECTOR_SIZE, + id_column={"name": "uuid", "data_type": "UUID"}, # type: ignore + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[ + {"name": "page", "data_type": "TEXT", "nullable": True}, + {"name": "source", "data_type": "TEXT", "nullable": True}, + ], + store_metadata=True, + overwrite_existing=True, + ) + with pytest.raises(TypeError): + await engine.ainit_vectorstore_table( + CUSTOM_TYPEDDICT_TABLE, + VECTOR_SIZE, + id_column={"name": "uuid", "data_type": "UUID", "nullable": False}, + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[ + {"name": "page", "nullable": True}, # type: ignore + {"data_type": "TEXT", "nullable": True}, # type: ignore + ], + store_metadata=True, + overwrite_existing=True, + ) + + async def test_init_table_custom_with_typed_dict(self, engine: PGEngine) -> None: + await engine.ainit_vectorstore_table( + CUSTOM_TYPEDDICT_TABLE, + VECTOR_SIZE, + id_column={"name": "uuid", "data_type": "UUID", "nullable": False}, + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[ + {"name": "page", "data_type": "TEXT", "nullable": True}, + {"name": "source", "data_type": "TEXT", "nullable": True}, + ], + store_metadata=True, + overwrite_existing=True, + ) + stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{CUSTOM_TYPEDDICT_TABLE}';" + results = await afetch(engine, stmt) + expected = [ + {"column_name": "uuid", "data_type": "uuid"}, + {"column_name": "my_embedding", "data_type": "USER-DEFINED"}, + {"column_name": "langchain_metadata", "data_type": "json"}, + {"column_name": "my-content", "data_type": "text"}, + {"column_name": "page", "data_type": "text"}, + {"column_name": "source", "data_type": "text"}, + ] + for row in results: + assert row in expected + + async def test_init_table_with_int_id(self, engine: PGEngine) -> None: + await engine.ainit_vectorstore_table( + INT_ID_CUSTOM_TABLE, + VECTOR_SIZE, + id_column=Column(name="integer_id", data_type="INTEGER", nullable=False), + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=True, + ) + stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{INT_ID_CUSTOM_TABLE}';" + results = await afetch(engine, stmt) + expected = [ + {"column_name": "integer_id", "data_type": "integer"}, + {"column_name": "my_embedding", "data_type": "USER-DEFINED"}, + {"column_name": "langchain_metadata", "data_type": "json"}, + {"column_name": "my-content", "data_type": "text"}, + {"column_name": "page", "data_type": "text"}, + {"column_name": "source", "data_type": "text"}, + ] + for row in results: + assert row in expected + + async def test_from_engine(self) -> None: + engine = create_async_engine( + CONNECTION_STRING, + ) + + pg_engine = PGEngine.from_engine(engine=engine) + await aexecute(pg_engine, "SELECT 1") + await pg_engine.close() + + async def test_from_connection_string(self) -> None: + engine = PGEngine.from_connection_string( + CONNECTION_STRING, + echo=True, + poolclass=NullPool, + ) + await aexecute(engine, "SELECT 1") + await engine.close() + + async def test_from_connection_string_url_error( + self, + ) -> None: + with pytest.raises(ValueError): + PGEngine.from_connection_string( + f"postgresql+pg8000://user:password@host:port/db_name", + ) + + async def test_column(self, engine: PGEngine) -> None: + with pytest.raises(ValueError): + Column("test", VARCHAR) # type: ignore + with pytest.raises(ValueError): + Column(1, "INTEGER") # type: ignore + + +@pytest.mark.enable_socket +@pytest.mark.asyncio +class TestEngineSync: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f'DROP TABLE "{CUSTOM_TABLE_SYNC}"') + await aexecute(engine, f'DROP TABLE "{DEFAULT_TABLE_SYNC}"') + await aexecute(engine, f'DROP TABLE "{INT_ID_CUSTOM_TABLE_SYNC}"') + await aexecute(engine, f'DROP TABLE "{CUSTOM_TYPEDDICT_TABLE_SYNC}"') + await engine.close() + + async def test_init_table(self, engine: PGEngine) -> None: + engine.init_vectorstore_table(DEFAULT_TABLE_SYNC, VECTOR_SIZE) + id = str(uuid.uuid4()) + content = "coffee" + embedding = await embeddings_service.aembed_query(content) + # Note: DeterministicFakeEmbedding generates a numpy array, converting to list a list of float values + embedding_string = [float(dimension) for dimension in embedding] + stmt = f"INSERT INTO {DEFAULT_TABLE_SYNC} (langchain_id, content, embedding) VALUES ('{id}', '{content}','{embedding_string}');" + await aexecute(engine, stmt) + + async def test_init_table_custom(self, engine: PGEngine) -> None: + engine.init_vectorstore_table( + CUSTOM_TABLE_SYNC, + VECTOR_SIZE, + id_column="uuid", + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=True, + ) + stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{CUSTOM_TABLE_SYNC}';" + results = await afetch(engine, stmt) + expected = [ + {"column_name": "uuid", "data_type": "uuid"}, + {"column_name": "my_embedding", "data_type": "USER-DEFINED"}, + {"column_name": "langchain_metadata", "data_type": "json"}, + {"column_name": "my-content", "data_type": "text"}, + {"column_name": "page", "data_type": "text"}, + {"column_name": "source", "data_type": "text"}, + ] + for row in results: + assert row in expected + + async def test_invalid_typed_dict(self, engine: PGEngine) -> None: + with pytest.raises(TypeError): + engine.init_vectorstore_table( + CUSTOM_TYPEDDICT_TABLE_SYNC, + VECTOR_SIZE, + id_column={"name": "uuid", "data_type": "UUID"}, # type: ignore + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[ + {"name": "page", "data_type": "TEXT", "nullable": True}, + {"name": "source", "data_type": "TEXT", "nullable": True}, + ], + store_metadata=True, + overwrite_existing=True, + ) + with pytest.raises(TypeError): + engine.init_vectorstore_table( + CUSTOM_TYPEDDICT_TABLE_SYNC, + VECTOR_SIZE, + id_column={"name": "uuid", "data_type": "UUID", "nullable": False}, + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[ + {"name": "page", "nullable": True}, # type: ignore + {"data_type": "TEXT", "nullable": True}, # type: ignore + ], + store_metadata=True, + overwrite_existing=True, + ) + + async def test_init_table_custom_with_typed_dict(self, engine: PGEngine) -> None: + engine.init_vectorstore_table( + CUSTOM_TYPEDDICT_TABLE_SYNC, + VECTOR_SIZE, + id_column={"name": "uuid", "data_type": "UUID", "nullable": False}, + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[ + {"name": "page", "data_type": "TEXT", "nullable": True}, + {"name": "source", "data_type": "TEXT", "nullable": True}, + ], + store_metadata=True, + ) + stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{CUSTOM_TYPEDDICT_TABLE_SYNC}';" + results = await afetch(engine, stmt) + expected = [ + {"column_name": "uuid", "data_type": "uuid"}, + {"column_name": "my_embedding", "data_type": "USER-DEFINED"}, + {"column_name": "langchain_metadata", "data_type": "json"}, + {"column_name": "my-content", "data_type": "text"}, + {"column_name": "page", "data_type": "text"}, + {"column_name": "source", "data_type": "text"}, + ] + for row in results: + assert row in expected + + async def test_init_table_with_int_id(self, engine: PGEngine) -> None: + engine.init_vectorstore_table( + INT_ID_CUSTOM_TABLE_SYNC, + VECTOR_SIZE, + id_column=Column(name="integer_id", data_type="INTEGER", nullable=False), + content_column="my-content", + embedding_column="my_embedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=True, + ) + stmt = f"SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '{INT_ID_CUSTOM_TABLE_SYNC}';" + results = await afetch(engine, stmt) + expected = [ + {"column_name": "integer_id", "data_type": "integer"}, + {"column_name": "my_embedding", "data_type": "USER-DEFINED"}, + {"column_name": "langchain_metadata", "data_type": "json"}, + {"column_name": "my-content", "data_type": "text"}, + {"column_name": "page", "data_type": "text"}, + {"column_name": "source", "data_type": "text"}, + ] + for row in results: + assert row in expected + + async def test_engine_constructor_key( + self, + engine: PGEngine, + ) -> None: + key = object() + with pytest.raises(Exception): + PGEngine(key, engine._pool, None, None) diff --git a/tests/unit_tests/v2/test_indexes.py b/tests/unit_tests/v2/test_indexes.py new file mode 100644 index 00000000..e22e0cd9 --- /dev/null +++ b/tests/unit_tests/v2/test_indexes.py @@ -0,0 +1,62 @@ +import warnings +import pytest + +from langchain_postgres.v2.indexes import ( + DistanceStrategy, + HNSWIndex, + HNSWQueryOptions, + IVFFlatIndex, + IVFFlatQueryOptions, +) + + +@pytest.mark.enable_socket +class TestPGIndex: + def test_distance_strategy(self) -> None: + assert DistanceStrategy.EUCLIDEAN.operator == "<->" + assert DistanceStrategy.EUCLIDEAN.search_function == "l2_distance" + assert DistanceStrategy.EUCLIDEAN.index_function == "vector_l2_ops" + + assert DistanceStrategy.COSINE_DISTANCE.operator == "<=>" + assert DistanceStrategy.COSINE_DISTANCE.search_function == "cosine_distance" + assert DistanceStrategy.COSINE_DISTANCE.index_function == "vector_cosine_ops" + + assert DistanceStrategy.INNER_PRODUCT.operator == "<#>" + assert DistanceStrategy.INNER_PRODUCT.search_function == "inner_product" + assert DistanceStrategy.INNER_PRODUCT.index_function == "vector_ip_ops" + + def test_hnsw_index(self) -> None: + index = HNSWIndex(name="test_index", m=32, ef_construction=128) + assert index.index_type == "hnsw" + assert index.m == 32 + assert index.ef_construction == 128 + assert index.index_options() == "(m = 32, ef_construction = 128)" + + def test_hnsw_query_options(self) -> None: + options = HNSWQueryOptions(ef_search=80) + assert options.to_parameter() == ["hnsw.ef_search = 80"] + + with warnings.catch_warnings(record=True) as w: + options.to_string() + + assert len(w) == 1 + assert "to_string is deprecated, use to_parameter instead." in str( + w[-1].message + ) + + def test_ivfflat_index(self) -> None: + index = IVFFlatIndex(name="test_index", lists=200) + assert index.index_type == "ivfflat" + assert index.lists == 200 + assert index.index_options() == "(lists = 200)" + + def test_ivfflat_query_options(self) -> None: + options = IVFFlatQueryOptions(probes=2) + assert options.to_parameter() == ["ivfflat.probes = 2"] + + with warnings.catch_warnings(record=True) as w: + options.to_string() + assert len(w) == 1 + assert "to_string is deprecated, use to_parameter instead." in str( + w[-1].message + ) diff --git a/tests/unit_tests/v2/test_pg_vectorstore.py b/tests/unit_tests/v2/test_pg_vectorstore.py new file mode 100644 index 00000000..d9765fa9 --- /dev/null +++ b/tests/unit_tests/v2/test_pg_vectorstore.py @@ -0,0 +1,486 @@ +import asyncio +import os +import uuid +from threading import Thread +from typing import AsyncIterator, Iterator, Sequence + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text +from sqlalchemy.engine.row import RowMapping +from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine + +from langchain_postgres import Column, PGEngine, PGVectorStore +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "test_table" + str(uuid.uuid4()) +DEFAULT_TABLE_SYNC = "test_table_sync" + str(uuid.uuid4()) +CUSTOM_TABLE = "test-table-custom" + str(uuid.uuid4()) +VECTOR_SIZE = 768 + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +texts = ["foo", "bar", "baz"] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query(texts[i]) for i in range(len(texts))] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async def run(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + await engine._run_as_async(run(engine, query)) + + +async def afetch(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async def run(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + result_fetch = result_map.fetchall() + return result_fetch + + return await engine._run_as_async(run(engine, query)) + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestVectorStore: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + + yield engine + await aexecute(engine, f'DROP TABLE IF EXISTS "{DEFAULT_TABLE}"') + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[PGVectorStore]: + await engine.ainit_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + ) + yield vs + + @pytest_asyncio.fixture(scope="class") + async def engine_sync(self) -> AsyncIterator[PGEngine]: + engine_sync = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine_sync + + await aexecute(engine_sync, f'DROP TABLE IF EXISTS "{DEFAULT_TABLE_SYNC}"') + await engine_sync.close() + + @pytest_asyncio.fixture(scope="class") + def vs_sync(self, engine_sync: PGEngine) -> Iterator[PGVectorStore]: + engine_sync.init_vectorstore_table(DEFAULT_TABLE_SYNC, VECTOR_SIZE) + + vs = PGVectorStore.create_sync( + engine_sync, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE_SYNC, + ) + yield vs + + @pytest_asyncio.fixture(scope="class") + async def vs_custom(self, engine: PGEngine) -> AsyncIterator[PGVectorStore]: + await engine.ainit_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + metadata_json_column="mymeta", + ) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + yield vs + await aexecute(engine, f'DROP TABLE IF EXISTS "{CUSTOM_TABLE}"') + + async def test_init_with_constructor(self, engine: PGEngine) -> None: + with pytest.raises(Exception): + PGVectorStore( # type: ignore + engine=engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="noname", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + + async def test_post_init(self, engine: PGEngine) -> None: + with pytest.raises(ValueError): + await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="noname", + embedding_column="myembedding", + metadata_columns=["page", "source"], + metadata_json_column="mymeta", + ) + + async def test_aadd_texts(self, engine: PGEngine, vs: PGVectorStore) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, metadatas, ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 6 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_cross_env_add_texts( + self, engine: PGEngine, vs: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + vs.add_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + vs.delete(ids) + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_texts_edge_cases( + self, engine: PGEngine, vs: PGVectorStore + ) -> None: + texts = ["Taylor's", '"Swift"', "best-friend"] + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_docs(self, engine: PGEngine, vs: PGVectorStore) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_documents(docs, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_embeddings( + self, engine: PGEngine, vs_custom: PGVectorStore + ) -> None: + await vs_custom.aadd_embeddings( + texts=texts, embeddings=embeddings, metadatas=metadatas + ) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_adelete(self, engine: PGEngine, vs: PGVectorStore) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 3 + # delete an ID + await vs.adelete([ids[0]]) + results = await afetch(engine, f'SELECT * FROM "{DEFAULT_TABLE}"') + assert len(results) == 2 + await aexecute(engine, f'TRUNCATE TABLE "{DEFAULT_TABLE}"') + + async def test_aadd_texts_custom( + self, engine: PGEngine, vs_custom: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_custom.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] is None + assert results[0]["source"] is None + + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_custom.aadd_texts(texts, metadatas, ids) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 6 + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_aadd_docs_custom( + self, engine: PGEngine, vs_custom: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + docs = [ + Document( + page_content=texts[i], + metadata={"page": str(i), "source": "postgres"}, + ) + for i in range(len(texts)) + ] + await vs_custom.aadd_documents(docs, ids=ids) + + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_adelete_custom( + self, engine: PGEngine, vs_custom: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_custom.aadd_texts(texts, ids=ids) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + content = [result["mycontent"] for result in results] + assert len(results) == 3 + assert "foo" in content + # delete an ID + await vs_custom.adelete([ids[0]]) + results = await afetch(engine, f'SELECT * FROM "{CUSTOM_TABLE}"') + content = [result["mycontent"] for result in results] + assert len(results) == 2 + assert "foo" not in content + await aexecute(engine, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_add_docs( + self, engine_sync: PGEngine, vs_sync: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + vs_sync.add_documents(docs, ids=ids) + results = await afetch(engine_sync, f'SELECT * FROM "{DEFAULT_TABLE_SYNC}"') + assert len(results) == 3 + vs_sync.delete(ids) + await aexecute(engine_sync, f'TRUNCATE TABLE "{DEFAULT_TABLE_SYNC}"') + + async def test_add_texts( + self, engine_sync: PGEngine, vs_sync: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + vs_sync.add_texts(texts, ids=ids) + results = await afetch(engine_sync, f'SELECT * FROM "{DEFAULT_TABLE_SYNC}"') + assert len(results) == 3 + await vs_sync.adelete(ids) + await aexecute(engine_sync, f'TRUNCATE TABLE "{DEFAULT_TABLE_SYNC}"') + + async def test_cross_env( + self, engine_sync: PGEngine, vs_sync: PGVectorStore + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await vs_sync.aadd_texts(texts, ids=ids) + results = await afetch(engine_sync, f'SELECT * FROM "{DEFAULT_TABLE_SYNC}"') + assert len(results) == 3 + await vs_sync.adelete(ids) + await aexecute(engine_sync, f'TRUNCATE TABLE "{DEFAULT_TABLE_SYNC}"') + + async def test_add_embeddings( + self, engine_sync: PGEngine, vs_custom: PGVectorStore + ) -> None: + vs_custom.add_embeddings( + texts=texts, + embeddings=embeddings, + metadatas=[ + {"page": str(i), "source": "postgres"} for i in range(len(texts)) + ], + ) + results = await afetch(engine_sync, f'SELECT * FROM "{CUSTOM_TABLE}"') + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine_sync, f'TRUNCATE TABLE "{CUSTOM_TABLE}"') + + async def test_create_vectorstore_with_invalid_parameters( + self, engine: PGEngine + ) -> None: + with pytest.raises(ValueError): + await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["random_column"], # invalid metadata column + ) + with pytest.raises(ValueError): + await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="langchain_id", # invalid content column type + embedding_column="myembedding", + metadata_columns=["random_column"], + ) + with pytest.raises(ValueError): + await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="random_column", # invalid embedding column + metadata_columns=["random_column"], + ) + with pytest.raises(ValueError): + await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="langchain_id", # invalid embedding column data type + metadata_columns=["random_column"], + ) + + async def test_from_engine(self) -> None: + async_engine = create_async_engine(url=CONNECTION_STRING) + + engine = PGEngine.from_engine(async_engine) + table_name = "test_table" + str(uuid.uuid4()).replace("-", "_") + await engine.ainit_vectorstore_table(table_name, VECTOR_SIZE) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 1 + + await aexecute(engine, f"DROP TABLE {table_name}") + await engine.close() + + async def test_from_engine_loop_connector( + self, + ) -> None: + async def init_connection_pool() -> AsyncEngine: + pool = create_async_engine(url=CONNECTION_STRING) + return pool + + loop = asyncio.new_event_loop() + thread = Thread(target=loop.run_forever, daemon=True) + thread.start() + + coro = init_connection_pool() + pool = asyncio.run_coroutine_threadsafe(coro, loop).result() + engine = PGEngine.from_engine(pool, loop) + table_name = "test_table" + str(uuid.uuid4()).replace("-", "_") + await engine.ainit_vectorstore_table(table_name, VECTOR_SIZE) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + vs.add_texts(["foo"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 2 + + await aexecute(engine, f"TRUNCATE TABLE {table_name}") + await engine.close() + + vs = PGVectorStore.create_sync( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + vs.add_texts(["foo"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 2 + + await aexecute(engine, f"DROP TABLE {table_name}") + + async def test_from_connection_string(self) -> None: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + table_name = "test_table" + str(uuid.uuid4()).replace("-", "_") + await engine.ainit_vectorstore_table(table_name, VECTOR_SIZE) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + vs.add_texts(["foo"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 2 + + await aexecute(engine, f"TRUNCATE TABLE {table_name}") + vs = PGVectorStore.create_sync( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + vs.add_texts(["bar"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 2 + await aexecute(engine, f"DROP TABLE {table_name}") + await engine.close() + + async def test_from_engine_loop(self) -> None: + loop = asyncio.new_event_loop() + thread = Thread(target=loop.run_forever, daemon=True) + thread.start() + pool = create_async_engine(url=CONNECTION_STRING) + engine = PGEngine.from_engine(pool, loop) + + table_name = "test_table" + str(uuid.uuid4()).replace("-", "_") + await engine.ainit_vectorstore_table(table_name, VECTOR_SIZE) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + vs.add_texts(["foo"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 2 + + await aexecute(engine, f"TRUNCATE TABLE {table_name}") + vs = PGVectorStore.create_sync( + engine, + embedding_service=embeddings_service, + table_name=table_name, + ) + await vs.aadd_texts(["foo"]) + vs.add_texts(["bar"]) + results = await afetch(engine, f"SELECT * FROM {table_name}") + assert len(results) == 2 + await aexecute(engine, f"DROP TABLE {table_name}") + await engine.close() + + @pytest.mark.filterwarnings("ignore") + def test_get_table_name(self, vs: PGVectorStore) -> None: + assert vs.get_table_name() == DEFAULT_TABLE diff --git a/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py b/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py new file mode 100644 index 00000000..e0320529 --- /dev/null +++ b/tests/unit_tests/v2/test_pg_vectorstore_from_methods.py @@ -0,0 +1,287 @@ +import os +import uuid +from typing import AsyncIterator, Sequence + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import VARCHAR, text +from sqlalchemy.engine.row import RowMapping +from sqlalchemy.ext.asyncio import create_async_engine + +from langchain_postgres import Column, PGEngine, PGVectorStore +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "default" + str(uuid.uuid4()).replace("-", "_") +DEFAULT_TABLE_SYNC = "default_sync" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE = "custom" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE_WITH_INT_ID = "custom_int_id" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE_WITH_INT_ID_SYNC = "custom_int_id_sync" + str(uuid.uuid4()).replace( + "-", "_" +) +VECTOR_SIZE = 768 + + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +texts = ["foo", "bar", "baz"] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query(texts[i]) for i in range(len(texts))] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async def run(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + await engine._run_as_async(run(engine, query)) + + +async def afetch(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async def run(engine: PGEngine, query: str) -> Sequence[RowMapping]: + async with engine._pool.connect() as conn: + result = await conn.execute(text(query)) + result_map = result.mappings() + result_fetch = result_map.fetchall() + return result_fetch + + return await engine._run_as_async(run(engine, query)) + + +@pytest.mark.enable_socket +@pytest.mark.asyncio +class TestVectorStoreFromMethods: + @pytest_asyncio.fixture + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + await engine.ainit_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + await engine.ainit_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=False, + ) + await engine.ainit_vectorstore_table( + CUSTOM_TABLE_WITH_INT_ID, + VECTOR_SIZE, + id_column=Column(name="integer_id", data_type="INTEGER", nullable=False), + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=False, + ) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE_WITH_INT_ID}") + await engine.close() + + @pytest_asyncio.fixture + async def engine_sync(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + engine.init_vectorstore_table(DEFAULT_TABLE_SYNC, VECTOR_SIZE) + engine.init_vectorstore_table( + CUSTOM_TABLE_WITH_INT_ID_SYNC, + VECTOR_SIZE, + id_column=Column(name="integer_id", data_type="INTEGER", nullable=False), + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[Column("page", "TEXT"), Column("source", "TEXT")], + store_metadata=False, + ) + + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE_SYNC}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE_WITH_INT_ID_SYNC}") + await engine.close() + + async def test_afrom_texts(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await PGVectorStore.afrom_texts( + texts, + embeddings_service, + engine, + DEFAULT_TABLE, + metadatas=metadatas, + ids=ids, + ) + results = await afetch(engine, f"SELECT * FROM {DEFAULT_TABLE}") + assert len(results) == 3 + await aexecute(engine, f"TRUNCATE TABLE {DEFAULT_TABLE}") + + async def test_from_texts(self, engine_sync: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + PGVectorStore.from_texts( + texts, + embeddings_service, + engine_sync, + DEFAULT_TABLE_SYNC, + metadatas=metadatas, + ids=ids, + ) + results = await afetch(engine_sync, f"SELECT * FROM {DEFAULT_TABLE_SYNC}") + assert len(results) == 3 + await aexecute(engine_sync, f"TRUNCATE TABLE {DEFAULT_TABLE_SYNC}") + + async def test_afrom_docs(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await PGVectorStore.afrom_documents( + docs, + embeddings_service, + engine, + DEFAULT_TABLE, + ids=ids, + ) + results = await afetch(engine, f"SELECT * FROM {DEFAULT_TABLE}") + assert len(results) == 3 + await aexecute(engine, f"TRUNCATE TABLE {DEFAULT_TABLE}") + + async def test_from_docs(self, engine_sync: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + PGVectorStore.from_documents( + docs, + embeddings_service, + engine_sync, + DEFAULT_TABLE_SYNC, + ids=ids, + ) + results = await afetch(engine_sync, f"SELECT * FROM {DEFAULT_TABLE_SYNC}") + assert len(results) == 3 + await aexecute(engine_sync, f"TRUNCATE TABLE {DEFAULT_TABLE_SYNC}") + + async def test_afrom_docs_cross_env(self, engine_sync: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await PGVectorStore.afrom_documents( + docs, + embeddings_service, + engine_sync, + DEFAULT_TABLE_SYNC, + ids=ids, + ) + results = await afetch(engine_sync, f"SELECT * FROM {DEFAULT_TABLE_SYNC}") + assert len(results) == 3 + await aexecute(engine_sync, f"TRUNCATE TABLE {DEFAULT_TABLE_SYNC}") + + async def test_from_docs_cross_env( + self, engine: PGEngine, engine_sync: PGEngine + ) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + PGVectorStore.from_documents( + docs, + embeddings_service, + engine, + DEFAULT_TABLE_SYNC, + ids=ids, + ) + results = await afetch(engine, f"SELECT * FROM {DEFAULT_TABLE_SYNC}") + assert len(results) == 3 + await aexecute(engine, f"TRUNCATE TABLE {DEFAULT_TABLE_SYNC}") + + async def test_afrom_texts_custom(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + await PGVectorStore.afrom_texts( + texts, + embeddings_service, + engine, + CUSTOM_TABLE, + ids=ids, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + results = await afetch(engine, f"SELECT * FROM {CUSTOM_TABLE}") + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] is None + assert results[0]["source"] is None + + async def test_afrom_docs_custom(self, engine: PGEngine) -> None: + ids = [str(uuid.uuid4()) for i in range(len(texts))] + docs = [ + Document( + page_content=texts[i], + metadata={"page": str(i), "source": "postgres"}, + ) + for i in range(len(texts)) + ] + await PGVectorStore.afrom_documents( + docs, + embeddings_service, + engine, + CUSTOM_TABLE, + ids=ids, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + + results = await afetch(engine, f"SELECT * FROM {CUSTOM_TABLE}") + assert len(results) == 3 + assert results[0]["mycontent"] == "foo" + assert results[0]["myembedding"] + assert results[0]["page"] == "0" + assert results[0]["source"] == "postgres" + await aexecute(engine, f"TRUNCATE TABLE {CUSTOM_TABLE}") + + async def test_afrom_texts_custom_with_int_id(self, engine: PGEngine) -> None: + ids = [i for i in range(len(texts))] + await PGVectorStore.afrom_texts( + texts, + embeddings_service, + engine, + CUSTOM_TABLE_WITH_INT_ID, + metadatas=metadatas, + ids=ids, + id_column="integer_id", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + results = await afetch(engine, f"SELECT * FROM {CUSTOM_TABLE_WITH_INT_ID}") + assert len(results) == 3 + for row in results: + assert isinstance(row["integer_id"], int) + await aexecute(engine, f"TRUNCATE TABLE {CUSTOM_TABLE_WITH_INT_ID}") + + async def test_from_texts_custom_with_int_id(self, engine_sync: PGEngine) -> None: + ids = [i for i in range(len(texts))] + PGVectorStore.from_texts( + texts, + embeddings_service, + engine_sync, + CUSTOM_TABLE_WITH_INT_ID_SYNC, + ids=ids, + id_column="integer_id", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=["page", "source"], + ) + results = await afetch( + engine_sync, f"SELECT * FROM {CUSTOM_TABLE_WITH_INT_ID_SYNC}" + ) + assert len(results) == 3 + for row in results: + assert isinstance(row["integer_id"], int) + await aexecute(engine_sync, f"TRUNCATE TABLE {CUSTOM_TABLE_WITH_INT_ID_SYNC}") diff --git a/tests/unit_tests/v2/test_pg_vectorstore_index.py b/tests/unit_tests/v2/test_pg_vectorstore_index.py new file mode 100644 index 00000000..fed78dcb --- /dev/null +++ b/tests/unit_tests/v2/test_pg_vectorstore_index.py @@ -0,0 +1,182 @@ +import os +import uuid +from typing import AsyncIterator + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text + +from langchain_postgres import PGEngine, PGVectorStore +from langchain_postgres.v2.indexes import ( + DEFAULT_INDEX_NAME_SUFFIX, + DistanceStrategy, + HNSWIndex, + IVFFlatIndex, +) +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +uuid_str = str(uuid.uuid4()).replace("-", "_") +uuid_str_sync = str(uuid.uuid4()).replace("-", "_") +DEFAULT_TABLE = "default" + uuid_str +DEFAULT_TABLE_ASYNC = "default_sync" + uuid_str_sync +CUSTOM_TABLE = "custom" + str(uuid.uuid4()).replace("-", "_") +DEFAULT_INDEX_NAME = "index" + uuid_str +DEFAULT_INDEX_NAME_ASYNC = "index" + uuid_str_sync +VECTOR_SIZE = 768 + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +texts = ["foo", "bar", "baz"] +ids = [str(uuid.uuid4()) for i in range(len(texts))] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query("foo") for i in range(len(texts))] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async def run(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + await engine._run_as_async(run(engine, query)) + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestIndex: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[PGVectorStore]: + engine.init_vectorstore_table(DEFAULT_TABLE, VECTOR_SIZE) + vs = PGVectorStore.create_sync( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + ) + + vs.add_texts(texts, ids=ids) + vs.drop_vector_index(DEFAULT_INDEX_NAME) + yield vs + + async def test_aapply_vector_index(self, vs: PGVectorStore) -> None: + index = HNSWIndex(name=DEFAULT_INDEX_NAME) + vs.apply_vector_index(index) + assert vs.is_valid_index(DEFAULT_INDEX_NAME) + vs.drop_vector_index(DEFAULT_INDEX_NAME) + + async def test_areindex(self, vs: PGVectorStore) -> None: + if not vs.is_valid_index(DEFAULT_INDEX_NAME): + index = HNSWIndex(name=DEFAULT_INDEX_NAME) + vs.apply_vector_index(index) + vs.reindex(DEFAULT_INDEX_NAME) + vs.reindex(DEFAULT_INDEX_NAME) + assert vs.is_valid_index(DEFAULT_INDEX_NAME) + vs.drop_vector_index(DEFAULT_INDEX_NAME) + + async def test_dropindex(self, vs: PGVectorStore) -> None: + vs.drop_vector_index(DEFAULT_INDEX_NAME) + result = vs.is_valid_index(DEFAULT_INDEX_NAME) + assert not result + + async def test_aapply_vector_index_ivfflat(self, vs: PGVectorStore) -> None: + index = IVFFlatIndex( + name=DEFAULT_INDEX_NAME, distance_strategy=DistanceStrategy.EUCLIDEAN + ) + vs.apply_vector_index(index, concurrently=True) + assert vs.is_valid_index(DEFAULT_INDEX_NAME) + index = IVFFlatIndex( + name="secondindex", + distance_strategy=DistanceStrategy.INNER_PRODUCT, + ) + vs.apply_vector_index(index) + assert vs.is_valid_index("secondindex") + vs.drop_vector_index("secondindex") + vs.drop_vector_index(DEFAULT_INDEX_NAME) + + async def test_is_valid_index(self, vs: PGVectorStore) -> None: + is_valid = vs.is_valid_index("invalid_index") + assert is_valid == False + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestAsyncIndex: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE_ASYNC}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[PGVectorStore]: + await engine.ainit_vectorstore_table(DEFAULT_TABLE_ASYNC, VECTOR_SIZE) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE_ASYNC, + ) + + await vs.aadd_texts(texts, ids=ids) + await vs.adrop_vector_index(DEFAULT_INDEX_NAME_ASYNC) + yield vs + + async def test_aapply_vector_index(self, vs: PGVectorStore) -> None: + index = HNSWIndex(name=DEFAULT_INDEX_NAME_ASYNC) + await vs.aapply_vector_index(index) + assert await vs.ais_valid_index(DEFAULT_INDEX_NAME_ASYNC) + + async def test_areindex(self, vs: PGVectorStore) -> None: + if not await vs.ais_valid_index(DEFAULT_INDEX_NAME_ASYNC): + index = HNSWIndex(name=DEFAULT_INDEX_NAME_ASYNC) + await vs.aapply_vector_index(index) + await vs.areindex(DEFAULT_INDEX_NAME_ASYNC) + await vs.areindex(DEFAULT_INDEX_NAME_ASYNC) + assert await vs.ais_valid_index(DEFAULT_INDEX_NAME_ASYNC) + await vs.adrop_vector_index(DEFAULT_INDEX_NAME_ASYNC) + + async def test_dropindex(self, vs: PGVectorStore) -> None: + await vs.adrop_vector_index(DEFAULT_INDEX_NAME_ASYNC) + result = await vs.ais_valid_index(DEFAULT_INDEX_NAME_ASYNC) + assert not result + + async def test_aapply_vector_index_ivfflat(self, vs: PGVectorStore) -> None: + index = IVFFlatIndex( + name=DEFAULT_INDEX_NAME_ASYNC, distance_strategy=DistanceStrategy.EUCLIDEAN + ) + await vs.aapply_vector_index(index, concurrently=True) + assert await vs.ais_valid_index(DEFAULT_INDEX_NAME_ASYNC) + index = IVFFlatIndex( + name="secondindex", + distance_strategy=DistanceStrategy.INNER_PRODUCT, + ) + await vs.aapply_vector_index(index) + assert await vs.ais_valid_index("secondindex") + await vs.adrop_vector_index("secondindex") + await vs.adrop_vector_index(DEFAULT_INDEX_NAME_ASYNC) + + async def test_is_valid_index(self, vs: PGVectorStore) -> None: + is_valid = await vs.ais_valid_index("invalid_index") + assert is_valid == False diff --git a/tests/unit_tests/v2/test_pg_vectorstore_search.py b/tests/unit_tests/v2/test_pg_vectorstore_search.py new file mode 100644 index 00000000..28d0f07f --- /dev/null +++ b/tests/unit_tests/v2/test_pg_vectorstore_search.py @@ -0,0 +1,433 @@ +import os +import uuid +from typing import AsyncIterator + +import pytest +import pytest_asyncio +from langchain_core.documents import Document +from langchain_core.embeddings import DeterministicFakeEmbedding +from sqlalchemy import text + +from langchain_postgres import Column, PGEngine, PGVectorStore +from langchain_postgres.v2.indexes import DistanceStrategy, HNSWQueryOptions +from tests.unit_tests.fixtures.metadata_filtering_data import ( + FILTERING_TEST_CASES, + METADATAS, + NEGATIVE_TEST_CASES, +) +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "default" + str(uuid.uuid4()).replace("-", "_") +DEFAULT_TABLE_SYNC = "default_sync" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_TABLE = "custom" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_FILTER_TABLE = "custom_filter" + str(uuid.uuid4()).replace("-", "_") +CUSTOM_FILTER_TABLE_SYNC = "custom_filter_sync" + str(uuid.uuid4()).replace("-", "_") +VECTOR_SIZE = 768 + +embeddings_service = DeterministicFakeEmbedding(size=VECTOR_SIZE) + +# Note: The following texts are chosen to produce diverse +# similarity scores when using the DeterministicFakeEmbedding service. This ensures +# that the test cases can effectively validate the filtering and scoring logic. +# The scoring might be different if using a different embedding service. +texts = ["foo", "bar", "baz", "boo"] +ids = [str(uuid.uuid4()) for i in range(len(texts))] +metadatas = [{"page": str(i), "source": "postgres"} for i in range(len(texts))] +docs = [ + Document(page_content=texts[i], metadata=metadatas[i]) for i in range(len(texts)) +] +filter_docs = [ + Document(page_content=texts[i], metadata=METADATAS[i]) for i in range(len(texts)) +] + +embeddings = [embeddings_service.embed_query("foo") for i in range(len(texts))] + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async def run(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + await engine._run_as_async(run(engine, query)) + + +@pytest.mark.enable_socket +@pytest.mark.asyncio(scope="class") +class TestVectorStoreSearch: + @pytest_asyncio.fixture(scope="class") + async def engine(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_FILTER_TABLE}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs(self, engine: PGEngine) -> AsyncIterator[PGVectorStore]: + await engine.ainit_vectorstore_table( + DEFAULT_TABLE, VECTOR_SIZE, store_metadata=False + ) + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + ) + + await vs.aadd_documents(docs, ids=ids) + yield vs + + @pytest_asyncio.fixture(scope="class") + async def engine_sync(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_TABLE}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs_custom(self, engine_sync: PGEngine) -> AsyncIterator[PGVectorStore]: + engine_sync.init_vectorstore_table( + CUSTOM_TABLE, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[ + Column("page", "TEXT"), + Column("source", "TEXT"), + ], + store_metadata=False, + ) + + vs_custom = PGVectorStore.create_sync( + engine_sync, + embedding_service=embeddings_service, + table_name=CUSTOM_TABLE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + index_query_options=HNSWQueryOptions(ef_search=1), + ) + vs_custom.add_documents(docs, ids=ids) + yield vs_custom + + @pytest_asyncio.fixture(scope="class") + async def vs_custom_filter(self, engine: PGEngine) -> AsyncIterator[PGVectorStore]: + await engine.ainit_vectorstore_table( + CUSTOM_FILTER_TABLE, + VECTOR_SIZE, + metadata_columns=[ + Column("name", "TEXT"), + Column("code", "TEXT"), + Column("price", "FLOAT"), + Column("is_available", "BOOLEAN"), + Column("tags", "TEXT[]"), + Column("inventory_location", "INTEGER[]"), + Column("available_quantity", "INTEGER", nullable=True), + ], + id_column="langchain_id", + store_metadata=False, + overwrite_existing=True, + ) + + vs_custom_filter = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=CUSTOM_FILTER_TABLE, + metadata_columns=[ + "name", + "code", + "price", + "is_available", + "tags", + "inventory_location", + "available_quantity", + ], + id_column="langchain_id", + ) + await vs_custom_filter.aadd_documents(filter_docs, ids=ids) + yield vs_custom_filter + + async def test_asimilarity_search(self, vs: PGVectorStore) -> None: + results = await vs.asimilarity_search("foo", k=1) + assert len(results) == 1 + assert results == [Document(page_content="foo", id=ids[0])] + results = await vs.asimilarity_search("foo", k=1, filter="content = 'bar'") + assert results == [Document(page_content="bar", id=ids[1])] + + async def test_asimilarity_search_score(self, vs: PGVectorStore) -> None: + results = await vs.asimilarity_search_with_score("foo") + assert len(results) == 4 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + assert results[0][1] == 0 + + async def test_asimilarity_search_by_vector(self, vs: PGVectorStore) -> None: + embedding = embeddings_service.embed_query("foo") + results = await vs.asimilarity_search_by_vector(embedding) + assert len(results) == 4 + assert results[0] == Document(page_content="foo", id=ids[0]) + result = await vs.asimilarity_search_with_score_by_vector(embedding=embedding) + assert result[0][0] == Document(page_content="foo", id=ids[0]) + assert result[0][1] == 0 + + async def test_similarity_search_with_relevance_scores_threshold_cosine( + self, vs: PGVectorStore + ) -> None: + score_threshold = {"score_threshold": 0} + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + # Note: Since tests use FakeEmbeddings which are non-normalized vectors, results might have scores beyond the range [0,1]. + # For a normalized embedding service, a threshold of zero will yield all matched documents. + assert len(results) == 2 + + score_threshold = {"score_threshold": 0.02} # type: ignore + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + assert len(results) == 2 + + score_threshold = {"score_threshold": 0.9} # type: ignore + results = await vs.asimilarity_search_with_relevance_scores( + "foo", **score_threshold + ) + assert len(results) == 1 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + + async def test_similarity_search_with_relevance_scores_threshold_euclidean( + self, engine: PGEngine + ) -> None: + vs = await PGVectorStore.create( + engine, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE, + distance_strategy=DistanceStrategy.EUCLIDEAN, + ) + + score_threshold = {"score_threshold": 0.9} + results = await vs.asimilarity_search_with_relevance_scores( + "foo", + **score_threshold, # type: ignore + ) + assert len(results) == 1 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + + async def test_amax_marginal_relevance_search(self, vs: PGVectorStore) -> None: + results = await vs.amax_marginal_relevance_search("bar") + assert results[0] == Document(page_content="bar", id=ids[1]) + results = await vs.amax_marginal_relevance_search( + "bar", filter="content = 'boo'" + ) + assert results[0] == Document(page_content="boo", id=ids[3]) + + async def test_amax_marginal_relevance_search_vector( + self, vs: PGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = await vs.amax_marginal_relevance_search_by_vector(embedding) + assert results[0] == Document(page_content="bar", id=ids[1]) + + async def test_amax_marginal_relevance_search_vector_score( + self, vs: PGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = await vs.amax_marginal_relevance_search_with_score_by_vector( + embedding + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + results = await vs.amax_marginal_relevance_search_with_score_by_vector( + embedding, lambda_mult=0.75, fetch_k=10 + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + async def test_aget_by_ids(self, vs: PGVectorStore) -> None: + test_ids = [ids[0]] + results = await vs.aget_by_ids(ids=test_ids) + + assert results[0] == Document(page_content="foo", id=ids[0]) + + async def test_aget_by_ids_custom_vs(self, vs_custom: PGVectorStore) -> None: + test_ids = [ids[0]] + results = await vs_custom.aget_by_ids(ids=test_ids) + + assert results[0] == Document(page_content="foo", id=ids[0]) + + @pytest.mark.parametrize("test_filter, expected_ids", FILTERING_TEST_CASES) + async def test_vectorstore_with_metadata_filters( + self, + vs_custom_filter: PGVectorStore, + test_filter: dict, + expected_ids: list[str], + ) -> None: + """Test end to end construction and search.""" + docs = await vs_custom_filter.asimilarity_search( + "meow", k=5, filter=test_filter + ) + assert [doc.metadata["code"] for doc in docs] == expected_ids, test_filter + + +@pytest.mark.enable_socket +class TestVectorStoreSearchSync: + @pytest_asyncio.fixture(scope="class") + async def engine_sync(self) -> AsyncIterator[PGEngine]: + engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield engine + await aexecute(engine, f"DROP TABLE IF EXISTS {DEFAULT_TABLE_SYNC}") + await aexecute(engine, f"DROP TABLE IF EXISTS {CUSTOM_FILTER_TABLE_SYNC}") + await engine.close() + + @pytest_asyncio.fixture(scope="class") + async def vs_custom(self, engine_sync: PGEngine) -> AsyncIterator[PGVectorStore]: + engine_sync.init_vectorstore_table( + DEFAULT_TABLE_SYNC, + VECTOR_SIZE, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + metadata_columns=[ + Column("page", "TEXT"), + Column("source", "TEXT"), + ], + store_metadata=False, + ) + + vs_custom = await PGVectorStore.create( + engine_sync, + embedding_service=embeddings_service, + table_name=DEFAULT_TABLE_SYNC, + id_column="myid", + content_column="mycontent", + embedding_column="myembedding", + index_query_options=HNSWQueryOptions(ef_search=1), + ) + vs_custom.add_documents(docs, ids=ids) + yield vs_custom + + @pytest_asyncio.fixture(scope="class") + async def vs_custom_filter_sync( + self, engine_sync: PGEngine + ) -> AsyncIterator[PGVectorStore]: + engine_sync.init_vectorstore_table( + CUSTOM_FILTER_TABLE_SYNC, + VECTOR_SIZE, + metadata_columns=[ + Column("name", "TEXT"), + Column("code", "TEXT"), + Column("price", "FLOAT"), + Column("is_available", "BOOLEAN"), + Column("tags", "TEXT[]"), + Column("inventory_location", "INTEGER[]"), + Column("available_quantity", "INTEGER", nullable=True), + ], + id_column="langchain_id", + store_metadata=False, + overwrite_existing=True, + ) + + vs_custom_filter_sync = await PGVectorStore.create( + engine_sync, + embedding_service=embeddings_service, + table_name=CUSTOM_FILTER_TABLE_SYNC, + metadata_columns=[ + "name", + "code", + "price", + "is_available", + "tags", + "inventory_location", + "available_quantity", + ], + id_column="langchain_id", + ) + + vs_custom_filter_sync.add_documents(filter_docs, ids=ids) + yield vs_custom_filter_sync + + def test_similarity_search(self, vs_custom: PGVectorStore) -> None: + results = vs_custom.similarity_search("foo", k=1) + assert len(results) == 1 + assert results == [Document(page_content="foo", id=ids[0])] + results = vs_custom.similarity_search("foo", k=1, filter="mycontent = 'bar'") + assert results == [Document(page_content="bar", id=ids[1])] + + def test_similarity_search_score(self, vs_custom: PGVectorStore) -> None: + results = vs_custom.similarity_search_with_score("foo") + assert len(results) == 4 + assert results[0][0] == Document(page_content="foo", id=ids[0]) + assert results[0][1] == 0 + + def test_similarity_search_by_vector(self, vs_custom: PGVectorStore) -> None: + embedding = embeddings_service.embed_query("foo") + results = vs_custom.similarity_search_by_vector(embedding) + assert len(results) == 4 + assert results[0] == Document(page_content="foo", id=ids[0]) + result = vs_custom.similarity_search_with_score_by_vector(embedding=embedding) + assert result[0][0] == Document(page_content="foo", id=ids[0]) + assert result[0][1] == 0 + + def test_max_marginal_relevance_search(self, vs_custom: PGVectorStore) -> None: + results = vs_custom.max_marginal_relevance_search("bar") + assert results[0] == Document(page_content="bar", id=ids[1]) + results = vs_custom.max_marginal_relevance_search( + "bar", filter="mycontent = 'boo'" + ) + assert results[0] == Document(page_content="boo", id=ids[3]) + + def test_max_marginal_relevance_search_vector( + self, vs_custom: PGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = vs_custom.max_marginal_relevance_search_by_vector(embedding) + assert results[0] == Document(page_content="bar", id=ids[1]) + + def test_max_marginal_relevance_search_vector_score( + self, vs_custom: PGVectorStore + ) -> None: + embedding = embeddings_service.embed_query("bar") + results = vs_custom.max_marginal_relevance_search_with_score_by_vector( + embedding + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + results = vs_custom.max_marginal_relevance_search_with_score_by_vector( + embedding, lambda_mult=0.75, fetch_k=10 + ) + assert results[0][0] == Document(page_content="bar", id=ids[1]) + + def test_get_by_ids_custom_vs(self, vs_custom: PGVectorStore) -> None: + test_ids = [ids[0]] + results = vs_custom.get_by_ids(ids=test_ids) + + assert results[0] == Document(page_content="foo", id=ids[0]) + + @pytest.mark.parametrize("test_filter, expected_ids", FILTERING_TEST_CASES) + def test_sync_vectorstore_with_metadata_filters( + self, + vs_custom_filter_sync: PGVectorStore, + test_filter: dict, + expected_ids: list[str], + ) -> None: + """Test end to end construction and search.""" + + docs = vs_custom_filter_sync.similarity_search("meow", k=5, filter=test_filter) + assert [doc.metadata["code"] for doc in docs] == expected_ids, test_filter + + @pytest.mark.parametrize("test_filter", NEGATIVE_TEST_CASES) + def test_metadata_filter_negative_tests( + self, vs_custom_filter_sync: PGVectorStore, test_filter: dict + ) -> None: + with pytest.raises((ValueError, NotImplementedError)): + docs = vs_custom_filter_sync.similarity_search( + "meow", k=5, filter=test_filter + ) diff --git a/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py b/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py new file mode 100644 index 00000000..4fcc9c43 --- /dev/null +++ b/tests/unit_tests/v2/test_pg_vectorstore_standard_suite.py @@ -0,0 +1,91 @@ +import os +import uuid +from typing import AsyncIterator, Iterator + +import pytest +import pytest_asyncio +from langchain_tests.integration_tests import VectorStoreIntegrationTests +from langchain_tests.integration_tests.vectorstores import EMBEDDING_SIZE +from sqlalchemy import text + +from langchain_postgres import Column, PGEngine, PGVectorStore +from tests.utils import VECTORSTORE_CONNECTION_STRING as CONNECTION_STRING + +DEFAULT_TABLE = "standard" + str(uuid.uuid4()) +DEFAULT_TABLE_SYNC = "sync_standard" + str(uuid.uuid4()) + + +def get_env_var(key: str, desc: str) -> str: + v = os.environ.get(key) + if v is None: + raise ValueError(f"Must set env var {key} to: {desc}") + return v + + +async def aexecute( + engine: PGEngine, + query: str, +) -> None: + async def run(engine: PGEngine, query: str) -> None: + async with engine._pool.connect() as conn: + await conn.execute(text(query)) + await conn.commit() + + await engine._run_as_async(run(engine, query)) + + +@pytest.mark.enable_socket +# @pytest.mark.filterwarnings("ignore") +@pytest.mark.asyncio +class TestStandardSuiteSync(VectorStoreIntegrationTests): + @pytest_asyncio.fixture(scope="function") + async def sync_engine(self) -> AsyncIterator[PGEngine]: + sync_engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield sync_engine + await aexecute(sync_engine, f'DROP TABLE IF EXISTS "{DEFAULT_TABLE_SYNC}"') + await sync_engine.close() + + @pytest.fixture(scope="function") + def vectorstore(self, sync_engine: PGEngine) -> PGVectorStore: # type: ignore + """Get an empty vectorstore for unit tests.""" + sync_engine.init_vectorstore_table( + DEFAULT_TABLE_SYNC, + EMBEDDING_SIZE, + id_column=Column(name="langchain_id", data_type="VARCHAR", nullable=False), + ) + + vs = PGVectorStore.create_sync( + sync_engine, + embedding_service=self.get_embeddings(), + table_name=DEFAULT_TABLE_SYNC, + ) + yield vs + + +@pytest.mark.enable_socket +# @pytest.mark.filterwarnings("ignore") +@pytest.mark.asyncio +class TestStandardSuiteAsync(VectorStoreIntegrationTests): + @pytest_asyncio.fixture(scope="function") + async def async_engine(self) -> AsyncIterator[PGEngine]: + async_engine = PGEngine.from_connection_string(url=CONNECTION_STRING) + yield async_engine + await aexecute(async_engine, f'DROP TABLE IF EXISTS "{DEFAULT_TABLE}"') + await async_engine.close() + + @pytest_asyncio.fixture(scope="function") + async def vectorstore(self, async_engine: PGEngine) -> AsyncIterator[PGVectorStore]: + """Get an empty vectorstore for unit tests.""" + await async_engine.ainit_vectorstore_table( + DEFAULT_TABLE, + EMBEDDING_SIZE, + id_column=Column(name="langchain_id", data_type="VARCHAR", nullable=False), + ) + + vs = await PGVectorStore.create( + async_engine, + embedding_service=self.get_embeddings(), + table_name=DEFAULT_TABLE, + ) + + yield vs diff --git a/tests/utils.py b/tests/utils.py index a61d38bc..54f80ca4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,5 @@ """Get fixtures for the database connection.""" + import os from contextlib import asynccontextmanager, contextmanager