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/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/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