From db3a153a98baa585b3a070cb794e2a4bf085c05c Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Fri, 15 Sep 2023 19:55:28 -0300 Subject: [PATCH 01/11] first version of products use case --- .gitignore | 3 + Dockerfile | 20 + README.md | 167 +- app/__init__.py | 0 app/config.py | 16 + app/handlers/__init__.py | 0 app/handlers/interface.py | 14 + app/handlers/products.py | 55 + app/indexer/interface.py | 14 + app/indexer/products.py | 99 ++ app/main.py | 47 + app/store/elasticsearch_vector_store.py | 22 + app/store/interface.py | 15 + app/tests/test_config.py | 39 + app/tests/test_elasticsearch_vector_store.py | 77 + app/tests/test_products_handler.py | 99 ++ app/tests/test_products_indexer.py | 102 ++ app/util.py | 14 + docker-compose.yml | 27 + entrypoint.sh | 2 + poetry.lock | 1656 ++++++++++++++++++ pyproject.toml | 21 + 22 files changed, 2508 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 app/__init__.py create mode 100644 app/config.py create mode 100644 app/handlers/__init__.py create mode 100644 app/handlers/interface.py create mode 100644 app/handlers/products.py create mode 100644 app/indexer/interface.py create mode 100644 app/indexer/products.py create mode 100644 app/main.py create mode 100644 app/store/elasticsearch_vector_store.py create mode 100644 app/store/interface.py create mode 100644 app/tests/test_config.py create mode 100644 app/tests/test_elasticsearch_vector_store.py create mode 100644 app/tests/test_products_handler.py create mode 100644 app/tests/test_products_indexer.py create mode 100644 app/util.py create mode 100644 docker-compose.yml create mode 100644 entrypoint.sh create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index 68bc17f..c322ddb 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.vscode +*cache* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..93f0713 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.10-slim + +WORKDIR /app + +RUN pip install poetry + +COPY pyproject.toml poetry.lock ./ + +RUN poetry config virtualenvs.create false && \ + poetry install --no-dev + +COPY . . + +EXPOSE 8000 + +COPY entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh + +CMD ["/entrypoint.sh"] diff --git a/README.md b/README.md index 8e0eef7..2e4979e 100644 --- a/README.md +++ b/README.md @@ -1 +1,166 @@ -# microservice-ia \ No newline at end of file +# SentenX + +microservice that uses a sentence transformer model to index and search records. + +## Table of Contents + +1. [Requirements](#requirements) +2. [Quickstart](#quickstart) +3. [Usage](#usage) + +## Requirements + +* python 3.101 +* elasticsearch 8.9.1 + +## Quickstart + +setup sagemaker required keys and elasticsearch url environment variables +``` +export AWS_ACCESS_KEY_ID=YOUR_SAGEMAKER_AWS_ACCESS_KEY +export AWS_SECRET_ACCESS_KEY=YOUR_SAGEMAKER_AWS_SECRET_ACCESS_KEY +export ELASTICSEARCH_URL=YOUR_ELASTICSEARCH_URL +``` + +install poetry +``` +pip install poetry +``` + +create a python 3.10 virtual environment +``` +poetry env user 3.10 +``` + +activate the environment +``` +poetry shell +``` + +install dependencies +``` +poetry install +``` + +start the microservice +``` +uvicorn app.main:main_app.api --reload +``` + +## Usage + +### To index a product + +request: +```bash +curl -X POST http://localhost:8000/products/index \ +-H 'Content-Type: application/json' \ +-d '{ + "id": "1", + "title": "leite em pó 200g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "asdfgh4321", + "product_retailer_id": "abc321" +}' +``` +response: +```json +{ + "page_content": "leite em pó 200g", + "metadata": { + "id": "1", + "title": "leite em pó 200g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "asdfgh4321", + "product_retailer_id": "abc321" + } +} +``` + +## To index products in batch + +request: +```bash + +curl -X POST http://localhost:8000/products/batch \ +-H 'Content-Type: application/json' \ +-d '[ + { + "id": "2", + "title": "chocolate em pó 200g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "asdfgh1234", + "product_retailer_id": "abc123" + }, + { + "id": "3", + "title": "café 250g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "zxcvbn5678", + "product_retailer_id": "def456" + } +]' +``` + +response: +```json +[ + { + "page_content": "chocolate em pó 200g", + "metadata": { + "id": "2", + "title": "chocolate em pó 200g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "asdfgh1234", + "product_retailer_id": "abc123" + } + }, + { + "page_content": "café 250g", + "metadata": { + "id": "3", + "title": "café 250g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "zxcvbn5678", + "product_retailer_id": "def456" + } + } +] +``` + +## To search for products + +request +```bash +curl http://localhost:8000/products/search \ +-H 'Content-Type: application/json' \ +-d '{ + "search": "leite em pó", + "filter": { + "channel_id": "5" + }, + "threshold": 1.6 +}' +``` +response: +```json +{ + "products": [ + { + "id": "1", + "title": "leite em pó 200g", + "org_id": "1", + "channel_id": "5", + "catalog_id": "asdfgh4321", + "product_retailer_id": "abc321", + "shop": null + } + ] +} +``` diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..72a88c7 --- /dev/null +++ b/app/config.py @@ -0,0 +1,16 @@ +import os + +class AppConfig: + def __init__(self): + self.product_index_name = os.environ.get("INDEX_NAME", "catalog_products") + self.es_url = os.environ.get("ELASTICSEARCH_URL", "http://localhost:9200") + self.embedding_type = os.environ.get("EMBEDDING_TYPE", "huggingface") + self.sagemaker = { + "endpoint_name": os.environ.get("SAGEMAKER_ENDPOINT_NAME", "huggingface-pytorch-inference-2023-07-28-21-01-20-147"), + "region_name": os.environ.get("SAGEMAKER_REGION_NAME", "us-east-1"), + } + self.huggingfacehub = { + "repo_id": os.environ.get("HUGGINGFACE_REPO_ID", "sentence-transformers/all-MiniLM-L6-v2"), + "task": os.environ.get("HUGGINGFACE_TASK","feature-extraction"), + "huggingfacehub_api_token": os.environ.get("HUGGINGFACE_API_TOKEN" ,"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") + } diff --git a/app/handlers/__init__.py b/app/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/handlers/interface.py b/app/handlers/interface.py new file mode 100644 index 0000000..862fd80 --- /dev/null +++ b/app/handlers/interface.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + +class IDocumentHandler(ABC): + @abstractmethod + def index(self): + pass + + @abstractmethod + def batch_index(self): + pass + + @abstractmethod + def search(self): + pass diff --git a/app/handlers/products.py b/app/handlers/products.py new file mode 100644 index 0000000..512e290 --- /dev/null +++ b/app/handlers/products.py @@ -0,0 +1,55 @@ +from fastapi import APIRouter, HTTPException +from fastapi.logger import logger +from app.handlers.interface import IDocumentHandler +from app.indexer.interface import IDocumentIndexer +from pydantic import BaseModel +from langchain.docstore.document import Document + +class Product(BaseModel): + id: str + title: str + org_id: str + channel_id: str + catalog_id: str + product_retailer_id: str + shop: str | None = None + +class ProductSearchRequest(BaseModel): + search: str + filter: dict[str, str] = None + threshold: float + +class ProductSearchResponse(BaseModel): + products: list[Product] + +class ProductsHandler(IDocumentHandler): + def __init__(self, product_indexer: IDocumentIndexer): + self.product_indexer = product_indexer + self.router = APIRouter() + self.router.add_api_route("/products/index", endpoint=self.index, methods=["POST"]) + self.router.add_api_route("/products/batch", endpoint=self.batch_index, methods=["POST"]) + self.router.add_api_route("/products/search", endpoint=self.search, methods=["GET"]) + + def index(self, product: Product): + try: + return self.product_indexer.index(product) + except Exception as e: + logger.error(msg=str(e)) + raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) + + def batch_index(self, products: list[Product]): + try: + return self.product_indexer.index_batch(products) + except Exception as e: + logger.error(msg=str(e)) + raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) + + def search(self, request: ProductSearchRequest): + try: + matched_products = self.product_indexer.search(request.search, request.filter, request.threshold) + return ProductSearchResponse( + products=matched_products + ) + except Exception as e: + logger.error(msg=str(e)) + raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) diff --git a/app/indexer/interface.py b/app/indexer/interface.py new file mode 100644 index 0000000..f0cc939 --- /dev/null +++ b/app/indexer/interface.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + +class IDocumentIndexer(ABC): + @abstractmethod + def index(self): + pass + + @abstractmethod + def index_batch(self): + pass + + @abstractmethod + def search(self): + pass diff --git a/app/indexer/products.py b/app/indexer/products.py new file mode 100644 index 0000000..5cdae38 --- /dev/null +++ b/app/indexer/products.py @@ -0,0 +1,99 @@ +# from langchain.docstore.document import Document +# from app.handlers.products import Product +# from app.indexer.interface import IDocumentIndexer +# from app.store.interface import IStorage + +# class ProductsIndexer(IDocumentIndexer): +# def __init__(self, storage: IStorage): +# self.storage = storage + +# def index(self, product: Product) -> Document: +# doc = Document( +# page_content=product.title, +# metadata={ +# "id": product.id, +# "title": product.title, +# "org_id": product.org_id, +# "channel_id": product.channel_id, +# "catalog_id": product.catalog_id, +# "product_retailer_id": product.product_retailer_id, +# } +# ) +# self.storage.save(doc) +# return doc + +# def index_batch(self, products: list[Product]) -> list[Document]: +# docs = [] +# for product in products: +# doc = Document( +# page_content=product.title, +# metadata={ +# "id": product.id, +# "title": product.title, +# "org_id": product.org_id, +# "channel_id": product.channel_id, +# "catalog_id": product.catalog_id, +# "product_retailer_id": product.product_retailer_id, +# } +# ) +# docs.append(doc) +# self.storage.save_batch(docs) +# return docs + +# def search(self, search, filter=None, threshold=0.1) -> list[Product]: +# matched_products = self.storage.search(search, filter, threshold) +# products = [] +# for p in matched_products: +# products.append(Product( +# id=p[0].metadata["id"], +# title=p[0].metadata["title"], +# org_id=p[0].metadata["org_id"], +# channel_id=p[0].metadata["channel_id"], +# catalog_id=p[0].metadata["catalog_id"], +# product_retailer_id=p[0].metadata["product_retailer_id"], +# )) +# return products +from langchain.docstore.document import Document +from app.handlers.products import Product +from app.indexer.interface import IDocumentIndexer +from app.store.interface import IStorage + +class ProductsIndexer(IDocumentIndexer): + def __init__(self, storage: IStorage): + self.storage = storage + + def _product_to_document(self, product: Product) -> Document: + metadata = { + "id": product.id, + "title": product.title, + "org_id": product.org_id, + "channel_id": product.channel_id, + "catalog_id": product.catalog_id, + "product_retailer_id": product.product_retailer_id, + } + return Document(page_content=product.title, metadata=metadata) + + def index(self, product: Product) -> Document: + doc = self._product_to_document(product) + self.storage.save(doc) + return doc + + def index_batch(self, products: list[Product]) -> list[Document]: + docs = [self._product_to_document(product) for product in products] + self.storage.save_batch(docs) + return docs + + def search(self, search, filter=None, threshold=0.1) -> list[Product]: + matched_documents = self.storage.search(search, filter, threshold) + products = [ + Product( + id=doc.metadata["id"], + title=doc.metadata["title"], + org_id=doc.metadata["org_id"], + channel_id=doc.metadata["channel_id"], + catalog_id=doc.metadata["catalog_id"], + product_retailer_id=doc.metadata["product_retailer_id"], + ) + for doc in matched_documents + ] + return products \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..0460798 --- /dev/null +++ b/app/main.py @@ -0,0 +1,47 @@ +from app.handlers.interface import IDocumentHandler +from app.handlers.products import ProductsHandler +from app.indexer.interface import IDocumentIndexer +from app.indexer.products import ProductsIndexer +from app.store.elasticsearch_vector_store import ElasticsearchVectorStoreIndex +from app.config import AppConfig +from app.util import ContentHandler + +from fastapi import FastAPI +from langchain.embeddings import SagemakerEndpointEmbeddings, HuggingFaceHubEmbeddings +from langchain.embeddings.base import Embeddings +from langchain.vectorstores import ElasticVectorSearch, VectorStore + +class App: + + api: FastAPI + config: AppConfig + embeddings: Embeddings + vectorstore: VectorStore + products_handler: IDocumentHandler + products_indexer: IDocumentIndexer + + def __init__(self, config:AppConfig): + self.config = config + if config.embedding_type == "sagemaker": + content_handler = ContentHandler() + self.embeddings = SagemakerEndpointEmbeddings( + endpoint_name= config.sagemaker["endpoint_name"], + region_name= config.sagemaker["region_name"], + content_handler=content_handler + ) + elif config.embedding_type == "huggingface": + self.embeddings = HuggingFaceHubEmbeddings( + repo_id=config.huggingfacehub["repo_id"], + task= config.huggingfacehub["task"], + huggingfacehub_api_token= config.huggingfacehub["huggingfacehub_api_token"], + ) + + self.api = FastAPI() + self.vectorstore = ElasticVectorSearch(elasticsearch_url=config.es_url, index_name=config.product_index_name, embedding=self.embeddings) + self.elasticStore = ElasticsearchVectorStoreIndex(self.vectorstore) + self.products_indexer = ProductsIndexer(self.elasticStore) + self.products_handler = ProductsHandler(self.products_indexer) + self.api.include_router(self.products_handler.router) + +config = AppConfig() +main_app = App(config) diff --git a/app/store/elasticsearch_vector_store.py b/app/store/elasticsearch_vector_store.py new file mode 100644 index 0000000..bebbd9c --- /dev/null +++ b/app/store/elasticsearch_vector_store.py @@ -0,0 +1,22 @@ +from langchain.vectorstores import VectorStore +from langchain.docstore.document import Document +from app.store.interface import IStorage + +class ElasticsearchVectorStoreIndex(IStorage): + def __init__(self, vectorstore: VectorStore, score=1.55): + self.vectorstore = vectorstore + self.score = score + + def save(self, doc: Document): + texts = [doc.page_content] + metadatas = [doc.metadata] + return self.vectorstore.add_texts(texts, metadatas) + + def save_batch(self, documents: list[Document]): + texts = [doc.page_content for doc in documents] + metadatas = [doc.metadata for doc in documents] + return self.vectorstore.add_texts(texts, metadatas) + + def search(self, search: str, filter=None, threshold=0.1) -> list[Document]: + sr = self.vectorstore.similarity_search_with_score(query=search, k=15, filter=filter) + return [doc[0] for doc in sr if doc[1] > threshold] diff --git a/app/store/interface.py b/app/store/interface.py new file mode 100644 index 0000000..51dfc4b --- /dev/null +++ b/app/store/interface.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod + +class IStorage(ABC): + @abstractmethod + def save(self): + pass + + @abstractmethod + def save_batch(self): + pass + + @abstractmethod + def search(self): + pass + diff --git a/app/tests/test_config.py b/app/tests/test_config.py new file mode 100644 index 0000000..94b6f40 --- /dev/null +++ b/app/tests/test_config.py @@ -0,0 +1,39 @@ +import os +import unittest +from app.config import AppConfig + +class TestAppConfig(unittest.TestCase): + def setUp(self): + os.environ["INDEX_NAME"] = "test_index" + os.environ["ELASTICSEARCH_URL"] = "http://test.elasticsearch.com" + os.environ["EMBEDDING_TYPE"] = "test_embedding" + os.environ["SAGEMAKER_ENDPOINT_NAME"] = "test_endpoint" + os.environ["SAGEMAKER_REGION_NAME"] = "test_region" + os.environ["HUGGINGFACE_REPO_ID"] = "test_repo_id" + os.environ["HUGGINGFACE_TASK"] = "test_task" + os.environ["HUGGINGFACE_API_TOKEN"] = "test_token" + + def tearDown(self): + del os.environ["INDEX_NAME"] + del os.environ["ELASTICSEARCH_URL"] + del os.environ["EMBEDDING_TYPE"] + del os.environ["SAGEMAKER_ENDPOINT_NAME"] + del os.environ["SAGEMAKER_REGION_NAME"] + del os.environ["HUGGINGFACE_REPO_ID"] + del os.environ["HUGGINGFACE_TASK"] + del os.environ["HUGGINGFACE_API_TOKEN"] + + def test_config_properties(self): + config = AppConfig() + + self.assertEqual(config.product_index_name, "test_index") + self.assertEqual(config.es_url, "http://test.elasticsearch.com") + self.assertEqual(config.embedding_type, "test_embedding") + self.assertEqual(config.sagemaker["endpoint_name"], "test_endpoint") + self.assertEqual(config.sagemaker["region_name"], "test_region") + self.assertEqual(config.huggingfacehub["repo_id"], "test_repo_id") + self.assertEqual(config.huggingfacehub["task"], "test_task") + self.assertEqual(config.huggingfacehub["huggingfacehub_api_token"], "test_token") + +if __name__ == "__main__": + unittest.main() diff --git a/app/tests/test_elasticsearch_vector_store.py b/app/tests/test_elasticsearch_vector_store.py new file mode 100644 index 0000000..6fd39a1 --- /dev/null +++ b/app/tests/test_elasticsearch_vector_store.py @@ -0,0 +1,77 @@ +from typing import Any, Iterable, List, Optional, Tuple +import unittest + +from langchain.vectorstores import VectorStore +from langchain.docstore.document import Document +from app.store.elasticsearch_vector_store import ElasticsearchVectorStoreIndex + +class ElasticsearchVectorStoreIndexTest(unittest.TestCase): + def setUp(self): + self.vectorstore = VectorStoreMock() + self.storage = ElasticsearchVectorStoreIndex(self.vectorstore) + + def test_save(self): + doc = Document(page_content="test doc", metadata={"id": "abc123"}) + self.storage.save(doc) + assert len(self.vectorstore.storage) == 1 + + def test_save_batch(self): + documents = [ + Document(page_content="first doc", metadata={"id": "abc123"}), + Document(page_content="second doc", metadata={"id": "Jane Doe"}) + ] + self.storage.save_batch(documents) + assert len(self.vectorstore.storage) == 2 + + def test_search(self): + self.vectorstore.add_texts(["test doc"], [{"id": "abc123"}]) + results = self.storage.search(search="test", filter={"id": "abc123"}) + assert len(results) == 1 + assert results[0][0].page_content == "test doc" + +if __name__ == "__main__": + unittest.main() + +class VectorStoreMock(VectorStore): + def __init__(self): + self.storage = [] + + def add_texts(self, texts: Iterable[str], metadatas: list[dict] | None = None, **kwargs: Any) -> List[str]: + i = 0 + for text in texts: + doc = Document(page_content=text, metadata=metadatas[i]) + self.storage.append(doc) + i=i+1 + + return texts + + def similarity_search(self, query: str, k: int = 4, **kwargs: Any) -> List[Document]: + return super().similarity_search(query, k, **kwargs) + + def from_texts( + self, + texts, + embedding, + metadatas = None, + **kwargs: Any, + ): + pass + + def similarity_search_with_score( + self, query: str, k: int = 4, filter: Optional[dict] = None, **kwargs: Any + ) -> List[Tuple[Document, float]]: + + matched_documents = [doc for doc in self.storage if query in doc.page_content.lower()] + + if filter is not None: + matched_documents = self._apply_filter(matched_documents, filter) + + return [(td, 1.6) for td in matched_documents] + + def _apply_filter(self, documents: list[Document], filter: dict) -> list[Document]: + filtered_documents = [] + for doc in documents: + matched = all(doc.metadata.get(k) == v for k, v in filter.items()) + if matched: + filtered_documents.append(doc) + return filtered_documents diff --git a/app/tests/test_products_handler.py b/app/tests/test_products_handler.py new file mode 100644 index 0000000..f64c7f6 --- /dev/null +++ b/app/tests/test_products_handler.py @@ -0,0 +1,99 @@ +import unittest +from unittest.mock import Mock +from app.handlers.products import ProductsHandler, Product, ProductSearchRequest, ProductSearchResponse +from app.indexer.interface import IDocumentIndexer +from langchain.docstore.document import Document + +class TestProductsHandler(unittest.TestCase): + def setUp(self): + self.mock_indexer = Mock(spec=IDocumentIndexer) + self.handler = ProductsHandler(self.mock_indexer) + + def test_index(self): + mock_product = Product( + id="1", + title="Test Product", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ) + mock_document = Document( + page_content=mock_product.title, + metadata={ + "id": mock_product.id, + "title": mock_product.title, + "org_id": mock_product.org_id, + "channel_id": mock_product.channel_id, + "catalog_id": mock_product.catalog_id, + "product_retailer_id": mock_product.product_retailer_id, + } + ) + self.mock_indexer.index.return_value = mock_document + + result = self.handler.index(mock_product) + + self.assertEqual(result, mock_document) + self.mock_indexer.index.assert_called_once_with(mock_product) + + def test_batch_index(self): + mock_products = [ + Product( + id="1", + title="Test Product 1", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ), + Product( + id="2", + title="Test Product 2", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ), + ] + mock_documents = [ + Document( + page_content=product.title, + metadata={ + "id": product.id, + "title": product.title, + "org_id": product.org_id, + "channel_id": product.channel_id, + "catalog_id": product.catalog_id, + "product_retailer_id": product.product_retailer_id, + } + ) + for product in mock_products + ] + self.mock_indexer.index_batch.return_value = mock_documents + + result = self.handler.batch_index(mock_products) + + self.assertEqual(result, mock_documents) + self.mock_indexer.index_batch.assert_called_once_with(mock_products) + + def test_search(self): + mock_request = ProductSearchRequest(search="Test", filter={"org_id": "123"}, threshold=0.5) + mock_product = Product( + id="1", + title="Test Product", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ) + self.mock_indexer.search.return_value = [mock_product] + + result = self.handler.search(mock_request) + + self.assertIsInstance(result, ProductSearchResponse) + self.assertEqual(len(result.products), 1) + self.assertEqual(result.products[0], mock_product) + self.mock_indexer.search.assert_called_once_with(mock_request.search, mock_request.filter, mock_request.threshold) + +if __name__ == "__main__": + unittest.main() diff --git a/app/tests/test_products_indexer.py b/app/tests/test_products_indexer.py new file mode 100644 index 0000000..22c6d6d --- /dev/null +++ b/app/tests/test_products_indexer.py @@ -0,0 +1,102 @@ +import unittest +from unittest.mock import Mock +from langchain.docstore.document import Document +from app.store.interface import IStorage +from app.indexer.products import ProductsIndexer +from app.handlers.products import Product + +class TestProductsIndexer(unittest.TestCase): + def setUp(self): + self.mock_storage = Mock(spec=IStorage) + self.indexer = ProductsIndexer(self.mock_storage) + + def test_index(self): + mock_product = Product( + id="1", + title="Test Product", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ) + mock_document = Document( + page_content=mock_product.title, + metadata={ + "id": mock_product.id, + "title": mock_product.title, + "org_id": mock_product.org_id, + "channel_id": mock_product.channel_id, + "catalog_id": mock_product.catalog_id, + "product_retailer_id": mock_product.product_retailer_id, + } + ) + self.mock_storage.save.return_value = mock_document + + result = self.indexer.index(mock_product) + + self.assertEqual(result, mock_document) + self.mock_storage.save.assert_called_once_with(mock_document) + + def test_index_batch(self): + mock_products = [ + Product( + id="1", + title="Test Product 1", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ), + Product( + id="2", + title="Test Product 2", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ), + ] + mock_documents = [ + Document( + page_content=product.title, + metadata={ + "id": product.id, + "title": product.title, + "org_id": product.org_id, + "channel_id": product.channel_id, + "catalog_id": product.catalog_id, + "product_retailer_id": product.product_retailer_id, + } + ) + for product in mock_products + ] + self.mock_storage.save_batch.return_value = mock_documents + + result = self.indexer.index_batch(mock_products) + + self.assertEqual(result, mock_documents) + self.mock_storage.save_batch.assert_called_once_with(mock_documents) + + def test_search(self): + mock_search_query = "Test Query" + mock_document = Document( + page_content="Document 1", + metadata={ + "id": "1", + "title": "Title 1", + "org_id": "123", + "channel_id": "456", + "catalog_id": "789", + "product_retailer_id": "999", + } + ) + self.mock_storage.search.return_value = [mock_document] + + result = self.indexer.search(mock_search_query) + + self.assertEqual(len(result), 1) + self.assertEqual(result[0].title, mock_document.metadata["title"]) + self.mock_storage.search.assert_called_once_with(mock_search_query, None, 0.1) + +if __name__ == "__main__": + unittest.main() diff --git a/app/util.py b/app/util.py new file mode 100644 index 0000000..cff5982 --- /dev/null +++ b/app/util.py @@ -0,0 +1,14 @@ +import json +from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler + +class ContentHandler(EmbeddingsContentHandler): + content_type = "application/json" + accepts = "application/json" + + def transform_input(self, inputs: list[str], model_kwargs: dict) -> bytes: + input_str = json.dumps({"inputs": inputs, **model_kwargs}) + return input_str.encode("utf-8") + + def transform_output(self, output: bytes) -> list[list[float]]: + response_json = json.loads(output.read().decode("utf-8")) + return response_json["vectors"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e39effa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3' +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + depends_on: + - elasticsearch + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.9.1 + ports: + - "9200:9200" + - "9300:9300" + environment: + - http.port=9200 + - discovery.type=single-node + - bootstrap.memory_lock=true + - ES_HEAP_SIZE=1g + - ES_JAVA_OPTS=-Xms1g -Xmx1g + - http.cors.enabled=true + - http.cors.allow-origin=* + - http.cors.allow-headers=X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization + - http.cors.allow-credentials=true + - network.publish_host=localhost + - xpack.security.enabled=false diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..73ac1c1 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec uvicorn app.main:main_app.api --host 0.0.0.0 --port 8000 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..59d55f6 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1656 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "aiohttp" +version = "3.8.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a94159871304770da4dd371f4291b20cac04e8c94f11bdea1c3478e557fbe0d8"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13bf85afc99ce6f9ee3567b04501f18f9f8dbbb2ea11ed1a2e079670403a7c84"}, + {file = "aiohttp-3.8.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ce2ac5708501afc4847221a521f7e4b245abf5178cf5ddae9d5b3856ddb2f3a"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96943e5dcc37a6529d18766597c491798b7eb7a61d48878611298afc1fca946c"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ad5c3c4590bb3cc28b4382f031f3783f25ec223557124c68754a2231d989e2b"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c413c633d0512df4dc7fd2373ec06cc6a815b7b6d6c2f208ada7e9e93a5061d"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df72ac063b97837a80d80dec8d54c241af059cc9bb42c4de68bd5b61ceb37caa"}, + {file = "aiohttp-3.8.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c48c5c0271149cfe467c0ff8eb941279fd6e3f65c9a388c984e0e6cf57538e14"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:368a42363c4d70ab52c2c6420a57f190ed3dfaca6a1b19afda8165ee16416a82"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7607ec3ce4993464368505888af5beb446845a014bc676d349efec0e05085905"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0d21c684808288a98914e5aaf2a7c6a3179d4df11d249799c32d1808e79503b5"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:312fcfbacc7880a8da0ae8b6abc6cc7d752e9caa0051a53d217a650b25e9a691"}, + {file = "aiohttp-3.8.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad093e823df03bb3fd37e7dec9d4670c34f9e24aeace76808fc20a507cace825"}, + {file = "aiohttp-3.8.5-cp310-cp310-win32.whl", hash = "sha256:33279701c04351a2914e1100b62b2a7fdb9a25995c4a104259f9a5ead7ed4802"}, + {file = "aiohttp-3.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:6e4a280e4b975a2e7745573e3fc9c9ba0d1194a3738ce1cbaa80626cc9b4f4df"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae871a964e1987a943d83d6709d20ec6103ca1eaf52f7e0d36ee1b5bebb8b9b9"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:461908b2578955045efde733719d62f2b649c404189a09a632d245b445c9c975"}, + {file = "aiohttp-3.8.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72a860c215e26192379f57cae5ab12b168b75db8271f111019509a1196dfc780"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc14be025665dba6202b6a71cfcdb53210cc498e50068bc088076624471f8bb9"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8af740fc2711ad85f1a5c034a435782fbd5b5f8314c9a3ef071424a8158d7f6b"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:841cd8233cbd2111a0ef0a522ce016357c5e3aff8a8ce92bcfa14cef890d698f"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed1c46fb119f1b59304b5ec89f834f07124cd23ae5b74288e364477641060ff"}, + {file = "aiohttp-3.8.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84f8ae3e09a34f35c18fa57f015cc394bd1389bce02503fb30c394d04ee6b938"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62360cb771707cb70a6fd114b9871d20d7dd2163a0feafe43fd115cfe4fe845e"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:23fb25a9f0a1ca1f24c0a371523546366bb642397c94ab45ad3aedf2941cec6a"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0ba0d15164eae3d878260d4c4df859bbdc6466e9e6689c344a13334f988bb53"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5d20003b635fc6ae3f96d7260281dfaf1894fc3aa24d1888a9b2628e97c241e5"}, + {file = "aiohttp-3.8.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0175d745d9e85c40dcc51c8f88c74bfbaef9e7afeeeb9d03c37977270303064c"}, + {file = "aiohttp-3.8.5-cp311-cp311-win32.whl", hash = "sha256:2e1b1e51b0774408f091d268648e3d57f7260c1682e7d3a63cb00d22d71bb945"}, + {file = "aiohttp-3.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:043d2299f6dfdc92f0ac5e995dfc56668e1587cea7f9aa9d8a78a1b6554e5755"}, + {file = "aiohttp-3.8.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cae533195e8122584ec87531d6df000ad07737eaa3c81209e85c928854d2195c"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f21e83f355643c345177a5d1d8079f9f28b5133bcd154193b799d380331d5d3"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a75ef35f2df54ad55dbf4b73fe1da96f370e51b10c91f08b19603c64004acc"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e2e9839e14dd5308ee773c97115f1e0a1cb1d75cbeeee9f33824fa5144c7634"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44e65da1de4403d0576473e2344828ef9c4c6244d65cf4b75549bb46d40b8dd"}, + {file = "aiohttp-3.8.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d847e4cde6ecc19125ccbc9bfac4a7ab37c234dd88fbb3c5c524e8e14da543"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:c7a815258e5895d8900aec4454f38dca9aed71085f227537208057853f9d13f2"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:8b929b9bd7cd7c3939f8bcfffa92fae7480bd1aa425279d51a89327d600c704d"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5db3a5b833764280ed7618393832e0853e40f3d3e9aa128ac0ba0f8278d08649"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:a0215ce6041d501f3155dc219712bc41252d0ab76474615b9700d63d4d9292af"}, + {file = "aiohttp-3.8.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fd1ed388ea7fbed22c4968dd64bab0198de60750a25fe8c0c9d4bef5abe13824"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win32.whl", hash = "sha256:6e6783bcc45f397fdebc118d772103d751b54cddf5b60fbcc958382d7dd64f3e"}, + {file = "aiohttp-3.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:b5411d82cddd212644cf9360879eb5080f0d5f7d809d03262c50dad02f01421a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:01d4c0c874aa4ddfb8098e85d10b5e875a70adc63db91f1ae65a4b04d3344cda"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5980a746d547a6ba173fd5ee85ce9077e72d118758db05d229044b469d9029a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a482e6da906d5e6e653be079b29bc173a48e381600161c9932d89dfae5942ef"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80bd372b8d0715c66c974cf57fe363621a02f359f1ec81cba97366948c7fc873"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1161b345c0a444ebcf46bf0a740ba5dcf50612fd3d0528883fdc0eff578006a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd56db019015b6acfaaf92e1ac40eb8434847d9bf88b4be4efe5bfd260aee692"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:153c2549f6c004d2754cc60603d4668899c9895b8a89397444a9c4efa282aaf4"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4a01951fabc4ce26ab791da5f3f24dca6d9a6f24121746eb19756416ff2d881b"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bfb9162dcf01f615462b995a516ba03e769de0789de1cadc0f916265c257e5d8"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7dde0009408969a43b04c16cbbe252c4f5ef4574ac226bc8815cd7342d2028b6"}, + {file = "aiohttp-3.8.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4149d34c32f9638f38f544b3977a4c24052042affa895352d3636fa8bffd030a"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win32.whl", hash = "sha256:68c5a82c8779bdfc6367c967a4a1b2aa52cd3595388bf5961a62158ee8a59e22"}, + {file = "aiohttp-3.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2cf57fb50be5f52bda004b8893e63b48530ed9f0d6c96c84620dc92fe3cd9b9d"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eca4bf3734c541dc4f374ad6010a68ff6c6748f00451707f39857f429ca36ced"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1274477e4c71ce8cfe6c1ec2f806d57c015ebf84d83373676036e256bc55d690"}, + {file = "aiohttp-3.8.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28c543e54710d6158fc6f439296c7865b29e0b616629767e685a7185fab4a6b9"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910bec0c49637d213f5d9877105d26e0c4a4de2f8b1b29405ff37e9fc0ad52b8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5443910d662db951b2e58eb70b0fbe6b6e2ae613477129a5805d0b66c54b6cb7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e460be6978fc24e3df83193dc0cc4de46c9909ed92dd47d349a452ef49325b7"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb1558def481d84f03b45888473fc5a1f35747b5f334ef4e7a571bc0dfcb11f8"}, + {file = "aiohttp-3.8.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34dd0c107799dcbbf7d48b53be761a013c0adf5571bf50c4ecad5643fe9cfcd0"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aa1990247f02a54185dc0dff92a6904521172a22664c863a03ff64c42f9b5410"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0e584a10f204a617d71d359fe383406305a4b595b333721fa50b867b4a0a1548"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a3cf433f127efa43fee6b90ea4c6edf6c4a17109d1d037d1a52abec84d8f2e42"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c11f5b099adafb18e65c2c997d57108b5bbeaa9eeee64a84302c0978b1ec948b"}, + {file = "aiohttp-3.8.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:84de26ddf621d7ac4c975dbea4c945860e08cccde492269db4e1538a6a6f3c35"}, + {file = "aiohttp-3.8.5-cp38-cp38-win32.whl", hash = "sha256:ab88bafedc57dd0aab55fa728ea10c1911f7e4d8b43e1d838a1739f33712921c"}, + {file = "aiohttp-3.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:5798a9aad1879f626589f3df0f8b79b3608a92e9beab10e5fda02c8a2c60db2e"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a6ce61195c6a19c785df04e71a4537e29eaa2c50fe745b732aa937c0c77169f3"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:773dd01706d4db536335fcfae6ea2440a70ceb03dd3e7378f3e815b03c97ab51"}, + {file = "aiohttp-3.8.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f83a552443a526ea38d064588613aca983d0ee0038801bc93c0c916428310c28"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f7372f7341fcc16f57b2caded43e81ddd18df53320b6f9f042acad41f8e049a"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea353162f249c8097ea63c2169dd1aa55de1e8fecbe63412a9bc50816e87b761"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d47ae48db0b2dcf70bc8a3bc72b3de86e2a590fc299fdbbb15af320d2659de"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d827176898a2b0b09694fbd1088c7a31836d1a505c243811c87ae53a3f6273c1"}, + {file = "aiohttp-3.8.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3562b06567c06439d8b447037bb655ef69786c590b1de86c7ab81efe1c9c15d8"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e874cbf8caf8959d2adf572a78bba17cb0e9d7e51bb83d86a3697b686a0ab4d"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6809a00deaf3810e38c628e9a33271892f815b853605a936e2e9e5129762356c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:33776e945d89b29251b33a7e7d006ce86447b2cfd66db5e5ded4e5cd0340585c"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eaeed7abfb5d64c539e2db173f63631455f1196c37d9d8d873fc316470dfbacd"}, + {file = "aiohttp-3.8.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e91d635961bec2d8f19dfeb41a539eb94bd073f075ca6dae6c8dc0ee89ad6f91"}, + {file = "aiohttp-3.8.5-cp39-cp39-win32.whl", hash = "sha256:00ad4b6f185ec67f3e6562e8a1d2b69660be43070bd0ef6fcec5211154c7df67"}, + {file = "aiohttp-3.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:c0a9034379a37ae42dea7ac1e048352d96286626251862e448933c0f59cbd79c"}, + {file = "aiohttp-3.8.5.tar.gz", hash = "sha256:b9552ec52cc147dbf1944ac7ac98af7602e51ea2dcd076ed194ca3c0d1c7d0bc"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "boto3" +version = "1.28.48" +description = "The AWS SDK for Python" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.28.48-py3-none-any.whl", hash = "sha256:ec7895504e3b2dd35fbdb7397bc3c48daaba8e6f37bc436aa928ff4e745f0f1c"}, + {file = "boto3-1.28.48.tar.gz", hash = "sha256:fed2d673fce33384697baa0028edfd18b06aa17af5c3ef82da75e9254a8ffb07"}, +] + +[package.dependencies] +botocore = ">=1.31.48,<1.32.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.31.48" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.31.48-py3-none-any.whl", hash = "sha256:9618c06f7e08ed590dae6613b8b2511055f7d6c07517382143ef8563169d4ef1"}, + {file = "botocore-1.31.48.tar.gz", hash = "sha256:6ed16f66aa6ed6070fed26d69764cb14c7759e4cc0b1c191283cc48b05d65de9"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.16.26)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dataclasses-json" +version = "0.5.9" +description = "Easily serialize dataclasses to and from JSON" +optional = false +python-versions = ">=3.6" +files = [ + {file = "dataclasses-json-0.5.9.tar.gz", hash = "sha256:e9ac87b73edc0141aafbce02b44e93553c3123ad574958f0fe52a534b6707e8e"}, + {file = "dataclasses_json-0.5.9-py3-none-any.whl", hash = "sha256:1280542631df1c375b7bc92e5b86d39e06c44760d7e3571a537b3b8acabf2f0c"}, +] + +[package.dependencies] +marshmallow = ">=3.3.0,<4.0.0" +marshmallow-enum = ">=1.5.1,<2.0.0" +typing-inspect = ">=0.4.0" + +[package.extras] +dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest (>=7.2.0)", "setuptools", "simplejson", "twine", "types-dataclasses", "wheel"] + +[[package]] +name = "elastic-transport" +version = "8.4.0" +description = "Transport classes and utilities shared among Python Elastic client libraries" +optional = false +python-versions = ">=3.6" +files = [ + {file = "elastic-transport-8.4.0.tar.gz", hash = "sha256:b9ad708ceb7fcdbc6b30a96f886609a109f042c0b9d9f2e44403b3133ba7ff10"}, + {file = "elastic_transport-8.4.0-py3-none-any.whl", hash = "sha256:19db271ab79c9f70f8c43f8f5b5111408781a6176b54ab2e54d713b6d9ceb815"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.2,<2" + +[package.extras] +develop = ["aiohttp", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "trustme"] + +[[package]] +name = "elasticsearch" +version = "8.9.0" +description = "Python client for Elasticsearch" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "elasticsearch-8.9.0-py3-none-any.whl", hash = "sha256:0795cbf0f61482070741c09ba02ac8fdf18f5984912fbd08b248fadd8a8c9952"}, + {file = "elasticsearch-8.9.0.tar.gz", hash = "sha256:d3367fc013e04fc7aad349a6de9fad1ee04fb6d627b0e7896aa505c12fde5e04"}, +] + +[package.dependencies] +elastic-transport = ">=8,<9" + +[package.extras] +async = ["aiohttp (>=3,<4)"] +requests = ["requests (>=2.4.0,<3.0.0)"] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.103.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.103.1-py3-none-any.whl", hash = "sha256:5e5f17e826dbd9e9b5a5145976c5cd90bcaa61f2bf9a69aca423f2bcebe44d83"}, + {file = "fastapi-0.103.1.tar.gz", hash = "sha256:345844e6a82062f06a096684196aaf96c1198b25c06b72c1311b882aa2d8a35d"}, +] + +[package.dependencies] +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] + +[[package]] +name = "frozenlist" +version = "1.4.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, + {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, + {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, + {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, + {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, + {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, + {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, + {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, + {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, + {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, + {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, + {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, + {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, + {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, + {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, + {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, + {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, + {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, + {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, + {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, + {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, + {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, +] + +[[package]] +name = "fsspec" +version = "2023.9.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2023.9.0-py3-none-any.whl", hash = "sha256:d55b9ab2a4c1f2b759888ae9f93e40c2aa72c0808132e87e282b549f9e6c4254"}, + {file = "fsspec-2023.9.0.tar.gz", hash = "sha256:4dbf0fefee035b7c6d3bbbe6bc99b2f201f40d4dca95b67c2b719be77bcd917f"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +devel = ["pytest", "pytest-cov"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +tqdm = ["tqdm"] + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "huggingface-hub" +version = "0.16.4" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "huggingface_hub-0.16.4-py3-none-any.whl", hash = "sha256:0d3df29932f334fead024afc7cb4cc5149d955238b8b5e42dcf9740d6995a349"}, + {file = "huggingface_hub-0.16.4.tar.gz", hash = "sha256:608c7d4f3d368b326d1747f91523dbd1f692871e8e2e7a4750314a2dd8b63e14"}, +] + +[package.dependencies] +filelock = "*" +fsspec = "*" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "black (>=23.1,<24.0)", "gradio", "jedi", "mypy (==0.982)", "numpy", "pydantic", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.0.241)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "black (>=23.1,<24.0)", "gradio", "jedi", "mypy (==0.982)", "numpy", "pydantic", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "ruff (>=0.0.241)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +inference = ["aiohttp", "pydantic"] +quality = ["black (>=23.1,<24.0)", "mypy (==0.982)", "ruff (>=0.0.241)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "numpy", "pydantic", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["torch"] +typing = ["pydantic", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "langchain" +version = "0.0.281" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain-0.0.281-py3-none-any.whl", hash = "sha256:2c30d41ee27edf9318d7e3a7b046a2b4a5fbbe00607d76569a50992820d00778"}, + {file = "langchain-0.0.281.tar.gz", hash = "sha256:751a86ad12a3758d13a5073f376a459d15d914ce6e43802f6ad22a21bcc4136d"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} +dataclasses-json = ">=0.5.7,<0.6.0" +langsmith = ">=0.0.21,<0.1.0" +numexpr = ">=2.8.4,<3.0.0" +numpy = ">=1,<2" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.6.8,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "libdeeplake (>=0.0.60,<0.0.61)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.5.0,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] +azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (>=0,<1)"] +clarifai = ["clarifai (>=9.1.0)"] +cohere = ["cohere (>=4,<5)"] +docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] +embeddings = ["sentence-transformers (>=2,<3)"] +extended-testing = ["amazon-textract-caller (<2)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "openai (>=0,<1)", "openapi-schema-pydantic (>=1.2,<2.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tqdm (>=4.48.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +javascript = ["esprima (>=4.0.1,<5.0.0)"] +llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] +openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.4.0)"] +qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +text-helpers = ["chardet (>=5.1.0,<6.0.0)"] + +[[package]] +name = "langsmith" +version = "0.0.37" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langsmith-0.0.37-py3-none-any.whl", hash = "sha256:bdfec3664162e672f89f9e4d82cbbd3f32587295d3064aab1746080a873ac3a0"}, + {file = "langsmith-0.0.37.tar.gz", hash = "sha256:a20e105329cae9a588414443b0a6eb56c776187e3aab47c327848bae0f1a4377"}, +] + +[package.dependencies] +pydantic = ">=1,<3" +requests = ">=2,<3" + +[[package]] +name = "marshmallow" +version = "3.20.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, + {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "marshmallow-enum" +version = "1.5.1" +description = "Enum field for Marshmallow" +optional = false +python-versions = "*" +files = [ + {file = "marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58"}, + {file = "marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"}, +] + +[package.dependencies] +marshmallow = ">=2.0.0" + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numexpr" +version = "2.8.6" +description = "Fast numerical expression evaluator for NumPy" +optional = false +python-versions = ">=3.7" +files = [ + {file = "numexpr-2.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80acbfefb68bd92e708e09f0a02b29e04d388b9ae72f9fcd57988aca172a7833"}, + {file = "numexpr-2.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e884687da8af5955dc9beb6a12d469675c90b8fb38b6c93668c989cfc2cd982"}, + {file = "numexpr-2.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ef7e8aaa84fce3aba2e65f243d14a9f8cc92aafd5d90d67283815febfe43eeb"}, + {file = "numexpr-2.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee04d72307c09599f786b9231acffb10df7d7a74b2ce3681d74a574880d13ce"}, + {file = "numexpr-2.8.6-cp310-cp310-win32.whl", hash = "sha256:211804ec25a9f6d188eadf4198dd1a92b2f61d7d20993c6c7706139bc4199c5b"}, + {file = "numexpr-2.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:18b1804923cfa3be7bbb45187d01c0540c8f6df4928c22a0f786e15568e9ebc5"}, + {file = "numexpr-2.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95b9da613761e4fc79748535b2a1f58cada22500e22713ae7d9571fa88d1c2e2"}, + {file = "numexpr-2.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:47b45da5aa25600081a649f5e8b2aa640e35db3703f4631f34bb1f2f86d1b5b4"}, + {file = "numexpr-2.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84979bf14143351c2db8d9dd7fef8aca027c66ad9df9cb5e75c93bf5f7b5a338"}, + {file = "numexpr-2.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d36528a33aa9c23743b3ea686e57526a4f71e7128a1be66210e1511b09c4e4e9"}, + {file = "numexpr-2.8.6-cp311-cp311-win32.whl", hash = "sha256:681812e2e71ff1ba9145fac42d03f51ddf6ba911259aa83041323f68e7458002"}, + {file = "numexpr-2.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:27782177a0081bd0aab229be5d37674e7f0ab4264ef576697323dd047432a4cd"}, + {file = "numexpr-2.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ef6e8896457a60a539cb6ba27da78315a9bb31edb246829b25b5b0304bfcee91"}, + {file = "numexpr-2.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e640bc0eaf1b59f3dde52bc02bbfda98e62f9950202b0584deba28baf9f36bbb"}, + {file = "numexpr-2.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d126938c2c3784673c9c58d94e00b1570aa65517d9c33662234d442fc9fb5795"}, + {file = "numexpr-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:e93d64cd20940b726477c3cb64926e683d31b778a1e18f9079a5088fd0d8e7c8"}, + {file = "numexpr-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:31cf610c952eec57081171f0b4427f9bed2395ec70ec432bbf45d260c5c0cdeb"}, + {file = "numexpr-2.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5f96c89aa0b1f13685ec32fa3d71028db0b5981bfd99a0bbc271035949136b3"}, + {file = "numexpr-2.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8f37f7a6af3bdd61f2efd1cafcc083a9525ab0aaf5dc641e7ec8fc0ae2d3aa1"}, + {file = "numexpr-2.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38b8b90967026bbc36c7aa6e8ca3b8906e1990914fd21f446e2a043f4ee3bc06"}, + {file = "numexpr-2.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1967c16f61c27df1cdc43ba3c0ba30346157048dd420b4259832276144d0f64e"}, + {file = "numexpr-2.8.6-cp38-cp38-win32.whl", hash = "sha256:15469dc722b5ceb92324ec8635411355ebc702303db901ae8cc87f47c5e3a124"}, + {file = "numexpr-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:95c09e814b0d6549de98b5ded7cdf7d954d934bb6b505432ff82e83a6d330bda"}, + {file = "numexpr-2.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa0f661f5f4872fd7350cc9895f5d2594794b2a7e7f1961649a351724c64acc9"}, + {file = "numexpr-2.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8e3e6f1588d6c03877cb3b3dcc3096482da9d330013b886b29cb9586af5af3eb"}, + {file = "numexpr-2.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8564186aad5a2c88d597ebc79b8171b52fd33e9b085013e1ff2208f7e4b387e3"}, + {file = "numexpr-2.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6a88d71c166e86b98d34701285d23e3e89d548d9f5ae3f4b60919ac7151949f"}, + {file = "numexpr-2.8.6-cp39-cp39-win32.whl", hash = "sha256:c48221b6a85494a7be5a022899764e58259af585dff031cecab337277278cc93"}, + {file = "numexpr-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:6d7003497d82ef19458dce380b36a99343b96a3bd5773465c2d898bf8f5a38f9"}, + {file = "numexpr-2.8.6.tar.gz", hash = "sha256:6336f8dba3f456e41a4ffc3c97eb63d89c73589ff6e1707141224b930263260d"}, +] + +[package.dependencies] +numpy = ">=1.13.3" + +[[package]] +name = "numpy" +version = "1.25.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pydantic" +version = "2.3.0" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"}, + {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.6.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.6.3" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"}, + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"}, + {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"}, + {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"}, + {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"}, + {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"}, + {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"}, + {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"}, + {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"}, + {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"}, + {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"}, + {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"}, + {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"}, + {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"}, + {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"}, + {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"}, + {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "regex" +version = "2023.8.8" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2023.8.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88900f521c645f784260a8d346e12a1590f79e96403971241e64c3a265c8ecdb"}, + {file = "regex-2023.8.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3611576aff55918af2697410ff0293d6071b7e00f4b09e005d614686ac4cd57c"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8a0ccc8f2698f120e9e5742f4b38dc944c38744d4bdfc427616f3a163dd9de5"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c662a4cbdd6280ee56f841f14620787215a171c4e2d1744c9528bed8f5816c96"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0633e4a1b667bfe0bb10b5e53fe0d5f34a6243ea2530eb342491f1adf4f739"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551ad543fa19e94943c5b2cebc54c73353ffff08228ee5f3376bd27b3d5b9800"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54de2619f5ea58474f2ac211ceea6b615af2d7e4306220d4f3fe690c91988a61"}, + {file = "regex-2023.8.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ec4b3f0aebbbe2fc0134ee30a791af522a92ad9f164858805a77442d7d18570"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ae646c35cb9f820491760ac62c25b6d6b496757fda2d51be429e0e7b67ae0ab"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca339088839582d01654e6f83a637a4b8194d0960477b9769d2ff2cfa0fa36d2"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d9b6627408021452dcd0d2cdf8da0534e19d93d070bfa8b6b4176f99711e7f90"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:bd3366aceedf274f765a3a4bc95d6cd97b130d1dda524d8f25225d14123c01db"}, + {file = "regex-2023.8.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7aed90a72fc3654fba9bc4b7f851571dcc368120432ad68b226bd593f3f6c0b7"}, + {file = "regex-2023.8.8-cp310-cp310-win32.whl", hash = "sha256:80b80b889cb767cc47f31d2b2f3dec2db8126fbcd0cff31b3925b4dc6609dcdb"}, + {file = "regex-2023.8.8-cp310-cp310-win_amd64.whl", hash = "sha256:b82edc98d107cbc7357da7a5a695901b47d6eb0420e587256ba3ad24b80b7d0b"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e7d84d64c84ad97bf06f3c8cb5e48941f135ace28f450d86af6b6512f1c9a71"}, + {file = "regex-2023.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce0f9fbe7d295f9922c0424a3637b88c6c472b75eafeaff6f910494a1fa719ef"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06c57e14ac723b04458df5956cfb7e2d9caa6e9d353c0b4c7d5d54fcb1325c46"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7a9aaa5a1267125eef22cef3b63484c3241aaec6f48949b366d26c7250e0357"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7408511fca48a82a119d78a77c2f5eb1b22fe88b0d2450ed0756d194fe7a9a"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14dc6f2d88192a67d708341f3085df6a4f5a0c7b03dec08d763ca2cd86e9f559"}, + {file = "regex-2023.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c640b99213643d141550326f34f0502fedb1798adb3c9eb79650b1ecb2f177"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0085da0f6c6393428bf0d9c08d8b1874d805bb55e17cb1dfa5ddb7cfb11140bf"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:964b16dcc10c79a4a2be9f1273fcc2684a9eedb3906439720598029a797b46e6"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7ce606c14bb195b0e5108544b540e2c5faed6843367e4ab3deb5c6aa5e681208"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:40f029d73b10fac448c73d6eb33d57b34607f40116e9f6e9f0d32e9229b147d7"}, + {file = "regex-2023.8.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3b8e6ea6be6d64104d8e9afc34c151926f8182f84e7ac290a93925c0db004bfd"}, + {file = "regex-2023.8.8-cp311-cp311-win32.whl", hash = "sha256:942f8b1f3b223638b02df7df79140646c03938d488fbfb771824f3d05fc083a8"}, + {file = "regex-2023.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:51d8ea2a3a1a8fe4f67de21b8b93757005213e8ac3917567872f2865185fa7fb"}, + {file = "regex-2023.8.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e951d1a8e9963ea51efd7f150450803e3b95db5939f994ad3d5edac2b6f6e2b4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704f63b774218207b8ccc6c47fcef5340741e5d839d11d606f70af93ee78e4d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22283c769a7b01c8ac355d5be0715bf6929b6267619505e289f792b01304d898"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91129ff1bb0619bc1f4ad19485718cc623a2dc433dff95baadbf89405c7f6b57"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de35342190deb7b866ad6ba5cbcccb2d22c0487ee0cbb251efef0843d705f0d4"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b993b6f524d1e274a5062488a43e3f9f8764ee9745ccd8e8193df743dbe5ee61"}, + {file = "regex-2023.8.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3026cbcf11d79095a32d9a13bbc572a458727bd5b1ca332df4a79faecd45281c"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:293352710172239bf579c90a9864d0df57340b6fd21272345222fb6371bf82b3"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d909b5a3fff619dc7e48b6b1bedc2f30ec43033ba7af32f936c10839e81b9217"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3d370ff652323c5307d9c8e4c62efd1956fb08051b0e9210212bc51168b4ff56"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:b076da1ed19dc37788f6a934c60adf97bd02c7eea461b73730513921a85d4235"}, + {file = "regex-2023.8.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e9941a4ada58f6218694f382e43fdd256e97615db9da135e77359da257a7168b"}, + {file = "regex-2023.8.8-cp36-cp36m-win32.whl", hash = "sha256:a8c65c17aed7e15a0c824cdc63a6b104dfc530f6fa8cb6ac51c437af52b481c7"}, + {file = "regex-2023.8.8-cp36-cp36m-win_amd64.whl", hash = "sha256:aadf28046e77a72f30dcc1ab185639e8de7f4104b8cb5c6dfa5d8ed860e57236"}, + {file = "regex-2023.8.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:423adfa872b4908843ac3e7a30f957f5d5282944b81ca0a3b8a7ccbbfaa06103"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae594c66f4a7e1ea67232a0846649a7c94c188d6c071ac0210c3e86a5f92109"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e51c80c168074faa793685656c38eb7a06cbad7774c8cbc3ea05552d615393d8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:09b7f4c66aa9d1522b06e31a54f15581c37286237208df1345108fcf4e050c18"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e73e5243af12d9cd6a9d6a45a43570dbe2e5b1cdfc862f5ae2b031e44dd95a8"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:941460db8fe3bd613db52f05259c9336f5a47ccae7d7def44cc277184030a116"}, + {file = "regex-2023.8.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f0ccf3e01afeb412a1a9993049cb160d0352dba635bbca7762b2dc722aa5742a"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2e9216e0d2cdce7dbc9be48cb3eacb962740a09b011a116fd7af8c832ab116ca"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cd9cd7170459b9223c5e592ac036e0704bee765706445c353d96f2890e816c8"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4873ef92e03a4309b3ccd8281454801b291b689f6ad45ef8c3658b6fa761d7ac"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:239c3c2a339d3b3ddd51c2daef10874410917cd2b998f043c13e2084cb191684"}, + {file = "regex-2023.8.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1005c60ed7037be0d9dea1f9c53cc42f836188227366370867222bda4c3c6bd7"}, + {file = "regex-2023.8.8-cp37-cp37m-win32.whl", hash = "sha256:e6bd1e9b95bc5614a7a9c9c44fde9539cba1c823b43a9f7bc11266446dd568e3"}, + {file = "regex-2023.8.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9a96edd79661e93327cfeac4edec72a4046e14550a1d22aa0dd2e3ca52aec921"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2181c20ef18747d5f4a7ea513e09ea03bdd50884a11ce46066bb90fe4213675"}, + {file = "regex-2023.8.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a2ad5add903eb7cdde2b7c64aaca405f3957ab34f16594d2b78d53b8b1a6a7d6"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9233ac249b354c54146e392e8a451e465dd2d967fc773690811d3a8c240ac601"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920974009fb37b20d32afcdf0227a2e707eb83fe418713f7a8b7de038b870d0b"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2b6c5dfe0929b6c23dde9624483380b170b6e34ed79054ad131b20203a1a63"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96979d753b1dc3b2169003e1854dc67bfc86edf93c01e84757927f810b8c3c93"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ae54a338191e1356253e7883d9d19f8679b6143703086245fb14d1f20196be9"}, + {file = "regex-2023.8.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2162ae2eb8b079622176a81b65d486ba50b888271302190870b8cc488587d280"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c884d1a59e69e03b93cf0dfee8794c63d7de0ee8f7ffb76e5f75be8131b6400a"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf9273e96f3ee2ac89ffcb17627a78f78e7516b08f94dc435844ae72576a276e"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:83215147121e15d5f3a45d99abeed9cf1fe16869d5c233b08c56cdf75f43a504"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f7454aa427b8ab9101f3787eb178057c5250478e39b99540cfc2b889c7d0586"}, + {file = "regex-2023.8.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0640913d2c1044d97e30d7c41728195fc37e54d190c5385eacb52115127b882"}, + {file = "regex-2023.8.8-cp38-cp38-win32.whl", hash = "sha256:0c59122ceccb905a941fb23b087b8eafc5290bf983ebcb14d2301febcbe199c7"}, + {file = "regex-2023.8.8-cp38-cp38-win_amd64.whl", hash = "sha256:c12f6f67495ea05c3d542d119d270007090bad5b843f642d418eb601ec0fa7be"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82cd0a69cd28f6cc3789cc6adeb1027f79526b1ab50b1f6062bbc3a0ccb2dbc3"}, + {file = "regex-2023.8.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb34d1605f96a245fc39790a117ac1bac8de84ab7691637b26ab2c5efb8f228c"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:987b9ac04d0b38ef4f89fbc035e84a7efad9cdd5f1e29024f9289182c8d99e09"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dd6082f4e2aec9b6a0927202c85bc1b09dcab113f97265127c1dc20e2e32495"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7eb95fe8222932c10d4436e7a6f7c99991e3fdd9f36c949eff16a69246dee2dc"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7098c524ba9f20717a56a8d551d2ed491ea89cbf37e540759ed3b776a4f8d6eb"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b694430b3f00eb02c594ff5a16db30e054c1b9589a043fe9174584c6efa8033"}, + {file = "regex-2023.8.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2aeab3895d778155054abea5238d0eb9a72e9242bd4b43f42fd911ef9a13470"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:988631b9d78b546e284478c2ec15c8a85960e262e247b35ca5eaf7ee22f6050a"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:67ecd894e56a0c6108ec5ab1d8fa8418ec0cff45844a855966b875d1039a2e34"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:14898830f0a0eb67cae2bbbc787c1a7d6e34ecc06fbd39d3af5fe29a4468e2c9"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f2200e00b62568cfd920127782c61bc1c546062a879cdc741cfcc6976668dfcf"}, + {file = "regex-2023.8.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9691a549c19c22d26a4f3b948071e93517bdf86e41b81d8c6ac8a964bb71e5a6"}, + {file = "regex-2023.8.8-cp39-cp39-win32.whl", hash = "sha256:6ab2ed84bf0137927846b37e882745a827458689eb969028af8032b1b3dac78e"}, + {file = "regex-2023.8.8-cp39-cp39-win_amd64.whl", hash = "sha256:5543c055d8ec7801901e1193a51570643d6a6ab8751b1f7dd9af71af467538bb"}, + {file = "regex-2023.8.8.tar.gz", hash = "sha256:fcbdc5f2b0f1cd0f6a56cdb46fe41d2cce1e644e3b68832f3eeebc5fb0f7712e"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "s3transfer" +version = "0.6.2" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.6.2-py3-none-any.whl", hash = "sha256:b014be3a8a2aab98cfe1abc7229cc5a9a0cf05eb9c1f2b86b230fd8df3f78084"}, + {file = "s3transfer-0.6.2.tar.gz", hash = "sha256:cab66d3380cca3e70939ef2255d01cd8aece6a4907a9528740f668c4b0611861"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.20" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"}, + {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"}, + {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "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.2.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "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)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, + {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, +] + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + +[[package]] +name = "tiktoken" +version = "0.4.0" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tiktoken-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:176cad7f053d2cc82ce7e2a7c883ccc6971840a4b5276740d0b732a2b2011f8a"}, + {file = "tiktoken-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:450d504892b3ac80207700266ee87c932df8efea54e05cefe8613edc963c1285"}, + {file = "tiktoken-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d662de1e7986d129139faf15e6a6ee7665ee103440769b8dedf3e7ba6ac37f"}, + {file = "tiktoken-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5727d852ead18b7927b8adf558a6f913a15c7766725b23dbe21d22e243041b28"}, + {file = "tiktoken-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c06cd92b09eb0404cedce3702fa866bf0d00e399439dad3f10288ddc31045422"}, + {file = "tiktoken-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9ec161e40ed44e4210d3b31e2ff426b4a55e8254f1023e5d2595cb60044f8ea6"}, + {file = "tiktoken-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:1e8fa13cf9889d2c928b9e258e9dbbbf88ab02016e4236aae76e3b4f82dd8288"}, + {file = "tiktoken-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb2341836b725c60d0ab3c84970b9b5f68d4b733a7bcb80fb25967e5addb9920"}, + {file = "tiktoken-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ca30367ad750ee7d42fe80079d3092bd35bb266be7882b79c3bd159b39a17b0"}, + {file = "tiktoken-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dc3df19ddec79435bb2a94ee46f4b9560d0299c23520803d851008445671197"}, + {file = "tiktoken-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d980fa066e962ef0f4dad0222e63a484c0c993c7a47c7dafda844ca5aded1f3"}, + {file = "tiktoken-0.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:329f548a821a2f339adc9fbcfd9fc12602e4b3f8598df5593cfc09839e9ae5e4"}, + {file = "tiktoken-0.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b1a038cee487931a5caaef0a2e8520e645508cde21717eacc9af3fbda097d8bb"}, + {file = "tiktoken-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:08efa59468dbe23ed038c28893e2a7158d8c211c3dd07f2bbc9a30e012512f1d"}, + {file = "tiktoken-0.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3020350685e009053829c1168703c346fb32c70c57d828ca3742558e94827a9"}, + {file = "tiktoken-0.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba16698c42aad8190e746cd82f6a06769ac7edd415d62ba027ea1d99d958ed93"}, + {file = "tiktoken-0.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c15d9955cc18d0d7ffcc9c03dc51167aedae98542238b54a2e659bd25fe77ed"}, + {file = "tiktoken-0.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64e1091c7103100d5e2c6ea706f0ec9cd6dc313e6fe7775ef777f40d8c20811e"}, + {file = "tiktoken-0.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e87751b54eb7bca580126353a9cf17a8a8eaadd44edaac0e01123e1513a33281"}, + {file = "tiktoken-0.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e063b988b8ba8b66d6cc2026d937557437e79258095f52eaecfafb18a0a10c03"}, + {file = "tiktoken-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9c6dd439e878172dc163fced3bc7b19b9ab549c271b257599f55afc3a6a5edef"}, + {file = "tiktoken-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d1d97f83697ff44466c6bef5d35b6bcdb51e0125829a9c0ed1e6e39fb9a08fb"}, + {file = "tiktoken-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b6bce7c68aa765f666474c7c11a7aebda3816b58ecafb209afa59c799b0dd2d"}, + {file = "tiktoken-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a73286c35899ca51d8d764bc0b4d60838627ce193acb60cc88aea60bddec4fd"}, + {file = "tiktoken-0.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0394967d2236a60fd0aacef26646b53636423cc9c70c32f7c5124ebe86f3093"}, + {file = "tiktoken-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:dae2af6f03ecba5f679449fa66ed96585b2fa6accb7fd57d9649e9e398a94f44"}, + {file = "tiktoken-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e251b1da3c293432179cf7c452cfa35562da286786be5a8b1ee3405c2b0dd2"}, + {file = "tiktoken-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:c835d0ee1f84a5aa04921717754eadbc0f0a56cf613f78dfc1cf9ad35f6c3fea"}, + {file = "tiktoken-0.4.0.tar.gz", hash = "sha256:59b20a819969735b48161ced9b92f05dc4519c17be4015cfb73b65270a243620"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "urllib3" +version = "1.26.16" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"}, + {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.23.2" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"}, + {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "ecc976c85bd6f407f006cf0946d15c4aa2e9991523102e3c44dc5b4ea2d691b2" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..81538c4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "microservice-ai" +version = "0.1.0" +description = "" +authors = ["Rafael Soares "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +langchain = "^0.0.281" +tiktoken = "^0.4.0" +elasticsearch = "^8.9.0" +boto3 = "^1.28.41" +fastapi = "^0.103.1" +uvicorn = "^0.23.2" +huggingface-hub = "^0.16.4" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From 3765d7df4b048def07fdd8db6e4cd245db5306ab Mon Sep 17 00:00:00 2001 From: Rafael Soares <75167930+rasoro@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:11:28 -0300 Subject: [PATCH 02/11] Update README.md --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2e4979e..4347f9d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ microservice that uses a sentence transformer model to index and search records. 1. [Requirements](#requirements) 2. [Quickstart](#quickstart) 3. [Usage](#usage) +4. [Test](#test). ## Requirements @@ -29,7 +30,7 @@ pip install poetry create a python 3.10 virtual environment ``` -poetry env user 3.10 +poetry env use 3.10 ``` activate the environment @@ -79,7 +80,7 @@ response: } ``` -## To index products in batch +### To index products in batch request: ```bash @@ -134,7 +135,7 @@ response: ] ``` -## To search for products +### To search for products request ```bash @@ -164,3 +165,10 @@ response: ] } ``` + +## Test + +we use unittest with discover to run the tests that are in `./app/tests` +``` +python -m unittest discover ./app/tests +``` From c866b6695321c614318d6b9c5b15a25c60268c6e Mon Sep 17 00:00:00 2001 From: Rafael Soares <75167930+rasoro@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:13:31 -0300 Subject: [PATCH 03/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4347f9d..4a312c6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ microservice that uses a sentence transformer model to index and search records. ## Requirements -* python 3.101 +* python 3.10 * elasticsearch 8.9.1 ## Quickstart From 48382a1766e527184da73599c1d40c79b50d34d6 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Wed, 20 Sep 2023 16:17:16 -0300 Subject: [PATCH 04/11] tweaks and use sagemaker as default --- README.md | 2 +- app/config.py | 4 ++-- app/main.py | 16 +++++++++------- app/tests/test_config.py | 4 ++-- app/tests/test_elasticsearch_vector_store.py | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4a312c6..e6914e7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ microservice that uses a sentence transformer model to index and search records. 1. [Requirements](#requirements) 2. [Quickstart](#quickstart) 3. [Usage](#usage) -4. [Test](#test). +4. [Test](#test) ## Requirements diff --git a/app/config.py b/app/config.py index 72a88c7..91cfa96 100644 --- a/app/config.py +++ b/app/config.py @@ -2,9 +2,9 @@ class AppConfig: def __init__(self): - self.product_index_name = os.environ.get("INDEX_NAME", "catalog_products") + self.product_index_name = os.environ.get("INDEX_PRODUCTS_NAME", "catalog_products") self.es_url = os.environ.get("ELASTICSEARCH_URL", "http://localhost:9200") - self.embedding_type = os.environ.get("EMBEDDING_TYPE", "huggingface") + self.embedding_type = os.environ.get("EMBEDDING_TYPE", "sagemaker") self.sagemaker = { "endpoint_name": os.environ.get("SAGEMAKER_ENDPOINT_NAME", "huggingface-pytorch-inference-2023-07-28-21-01-20-147"), "region_name": os.environ.get("SAGEMAKER_REGION_NAME", "us-east-1"), diff --git a/app/main.py b/app/main.py index 0460798..1968116 100644 --- a/app/main.py +++ b/app/main.py @@ -22,19 +22,20 @@ class App: def __init__(self, config:AppConfig): self.config = config - if config.embedding_type == "sagemaker": + if config.embedding_type == "huggingface": + self.embeddings = HuggingFaceHubEmbeddings( + repo_id=config.huggingfacehub["repo_id"], + task= config.huggingfacehub["task"], + huggingfacehub_api_token= config.huggingfacehub["huggingfacehub_api_token"], + ) + else: # sagemaker by default content_handler = ContentHandler() self.embeddings = SagemakerEndpointEmbeddings( endpoint_name= config.sagemaker["endpoint_name"], region_name= config.sagemaker["region_name"], content_handler=content_handler ) - elif config.embedding_type == "huggingface": - self.embeddings = HuggingFaceHubEmbeddings( - repo_id=config.huggingfacehub["repo_id"], - task= config.huggingfacehub["task"], - huggingfacehub_api_token= config.huggingfacehub["huggingfacehub_api_token"], - ) + self.api = FastAPI() self.vectorstore = ElasticVectorSearch(elasticsearch_url=config.es_url, index_name=config.product_index_name, embedding=self.embeddings) @@ -45,3 +46,4 @@ def __init__(self, config:AppConfig): config = AppConfig() main_app = App(config) +print(config.embedding_type) diff --git a/app/tests/test_config.py b/app/tests/test_config.py index 94b6f40..1fd12c4 100644 --- a/app/tests/test_config.py +++ b/app/tests/test_config.py @@ -4,7 +4,7 @@ class TestAppConfig(unittest.TestCase): def setUp(self): - os.environ["INDEX_NAME"] = "test_index" + os.environ["INDEX_PRODUCTS_NAME"] = "test_index" os.environ["ELASTICSEARCH_URL"] = "http://test.elasticsearch.com" os.environ["EMBEDDING_TYPE"] = "test_embedding" os.environ["SAGEMAKER_ENDPOINT_NAME"] = "test_endpoint" @@ -14,7 +14,7 @@ def setUp(self): os.environ["HUGGINGFACE_API_TOKEN"] = "test_token" def tearDown(self): - del os.environ["INDEX_NAME"] + del os.environ["INDEX_PRODUCTS_NAME"] del os.environ["ELASTICSEARCH_URL"] del os.environ["EMBEDDING_TYPE"] del os.environ["SAGEMAKER_ENDPOINT_NAME"] diff --git a/app/tests/test_elasticsearch_vector_store.py b/app/tests/test_elasticsearch_vector_store.py index 6fd39a1..bdf73ff 100644 --- a/app/tests/test_elasticsearch_vector_store.py +++ b/app/tests/test_elasticsearch_vector_store.py @@ -27,7 +27,7 @@ def test_search(self): self.vectorstore.add_texts(["test doc"], [{"id": "abc123"}]) results = self.storage.search(search="test", filter={"id": "abc123"}) assert len(results) == 1 - assert results[0][0].page_content == "test doc" + assert results[0].page_content == "test doc" if __name__ == "__main__": unittest.main() From 0e20012f82a7b3fbbce97154bac426ceed782a24 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Fri, 22 Sep 2023 21:08:25 -0300 Subject: [PATCH 05/11] repo/project configs --- .coveragerc | 2 + .gitignore | 2 + poetry.lock | 149 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 4 ++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..4b5d00b --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[report] +exclude_lines = pass diff --git a/.gitignore b/.gitignore index c322ddb..911b096 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,5 @@ cython_debug/ .vscode *cache* +.coverage +htmlcov diff --git a/poetry.lock b/poetry.lock index 59d55f6..1aaf9bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -183,6 +183,52 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "black" +version = "23.9.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "boto3" version = "1.28.48" @@ -341,6 +387,70 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.3.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, +] + +[package.extras] +toml = ["tomli"] + [[package]] name = "dataclasses-json" version = "0.5.9" @@ -961,6 +1071,32 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pydantic" version = "2.3.0" @@ -1482,6 +1618,17 @@ requests = ">=2.26.0" [package.extras] blobfile = ["blobfile (>=2)"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "tqdm" version = "4.66.1" @@ -1653,4 +1800,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "ecc976c85bd6f407f006cf0946d15c4aa2e9991523102e3c44dc5b4ea2d691b2" +content-hash = "c1782e5264a0a0a66f044c4babc8b5ed9b17ba0961f5e2bb81feae5df29bda49" diff --git a/pyproject.toml b/pyproject.toml index 81538c4..5043170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,10 @@ uvicorn = "^0.23.2" huggingface-hub = "^0.16.4" +[tool.poetry.group.dev.dependencies] +coverage = "^7.3.1" +black = "^23.9.1" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From a7da2d5024b4a50551c75ec245c518f4b755f2dc Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Fri, 22 Sep 2023 21:09:57 -0300 Subject: [PATCH 06/11] refactor index to also update + delete + tests --- app/handlers/__init__.py | 22 +++ app/handlers/interface.py | 14 -- app/handlers/products.py | 74 +++++++-- app/indexer/{interface.py => __init__.py} | 8 + app/indexer/products.py | 131 +++++---------- app/main.py | 5 +- app/store/__init__.py | 24 +++ app/store/elasticsearch_vector_store.py | 38 ++++- app/store/interface.py | 15 -- app/tests/test_config.py | 3 - app/tests/test_elasticsearch_vector_store.py | 103 +++++------- app/tests/test_products_handler.py | 166 ++++++++++++++----- app/tests/test_products_indexer.py | 134 +++++++++++---- 13 files changed, 458 insertions(+), 279 deletions(-) delete mode 100644 app/handlers/interface.py rename app/indexer/{interface.py => __init__.py} (66%) create mode 100644 app/store/__init__.py delete mode 100644 app/store/interface.py diff --git a/app/handlers/__init__.py b/app/handlers/__init__.py index e69de29..c6e0633 100644 --- a/app/handlers/__init__.py +++ b/app/handlers/__init__.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod + +class IDocumentHandler(ABC): + @abstractmethod + def index(self): + pass + + @abstractmethod + def batch_index(self): + pass + + @abstractmethod + def search(self): + pass + + @abstractmethod + def delete(self): + pass + + @abstractmethod + def delete_batch(self): + pass diff --git a/app/handlers/interface.py b/app/handlers/interface.py deleted file mode 100644 index 862fd80..0000000 --- a/app/handlers/interface.py +++ /dev/null @@ -1,14 +0,0 @@ -from abc import ABC, abstractmethod - -class IDocumentHandler(ABC): - @abstractmethod - def index(self): - pass - - @abstractmethod - def batch_index(self): - pass - - @abstractmethod - def search(self): - pass diff --git a/app/handlers/products.py b/app/handlers/products.py index 512e290..2cdc6a8 100644 --- a/app/handlers/products.py +++ b/app/handlers/products.py @@ -1,45 +1,72 @@ from fastapi import APIRouter, HTTPException from fastapi.logger import logger -from app.handlers.interface import IDocumentHandler -from app.indexer.interface import IDocumentIndexer +from app.handlers import IDocumentHandler +from app.indexer import IDocumentIndexer from pydantic import BaseModel -from langchain.docstore.document import Document class Product(BaseModel): - id: str title: str org_id: str channel_id: str catalog_id: str product_retailer_id: str - shop: str | None = None + + @staticmethod + def from_metadata(metadata:dict): + return Product( + title=metadata["title"], + org_id=metadata["org_id"], + channel_id=metadata["channel_id"], + catalog_id=metadata["catalog_id"], + product_retailer_id=metadata["product_retailer_id"], + ) + +class ProductIndexRequest(BaseModel): + catalog_id: str + product: Product = None + +class ProductBatchIndexRequest(BaseModel): + catalog_id: str + products: list[Product] = None + +class ProductIndexResponse(BaseModel): + catalog_id: str + documents: list[str] class ProductSearchRequest(BaseModel): search: str filter: dict[str, str] = None - threshold: float + threshold: float = 1.5 class ProductSearchResponse(BaseModel): products: list[Product] +class ProductDeleteRequest(BaseModel): + catalog_id: str + product_retailer_ids: list[str] + class ProductsHandler(IDocumentHandler): def __init__(self, product_indexer: IDocumentIndexer): self.product_indexer = product_indexer self.router = APIRouter() - self.router.add_api_route("/products/index", endpoint=self.index, methods=["POST"]) - self.router.add_api_route("/products/batch", endpoint=self.batch_index, methods=["POST"]) + self.router.add_api_route("/products/index", endpoint=self.index, methods=["PUT"]) + self.router.add_api_route("/products/batch", endpoint=self.batch_index, methods=["PUT"]) self.router.add_api_route("/products/search", endpoint=self.search, methods=["GET"]) - - def index(self, product: Product): + self.router.add_api_route("/products/index", endpoint=self.delete, methods=["DELETE"]) + self.router.add_api_route("/products/batch", endpoint=self.delete_batch, methods=["DELETE"]) + + def index(self, request: ProductIndexRequest): try: - return self.product_indexer.index(product) + docs = self.product_indexer.index(request.catalog_id, request.product) + return ProductIndexResponse(catalog_id=request.catalog_id, documents=docs) except Exception as e: logger.error(msg=str(e)) raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) - - def batch_index(self, products: list[Product]): + + def batch_index(self, request: ProductBatchIndexRequest): try: - return self.product_indexer.index_batch(products) + docs = self.product_indexer.index_batch(request.catalog_id, request.products) + return ProductIndexResponse(catalog_id=request.catalog_id, documents=docs) except Exception as e: logger.error(msg=str(e)) raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) @@ -50,6 +77,25 @@ def search(self, request: ProductSearchRequest): return ProductSearchResponse( products=matched_products ) + except Exception as e: + logger.error(msg=str(e)) + raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) + + def delete(self, catalog_id: str="", product_retailer_id: str="", request: ProductDeleteRequest = None): + try: + if request is not None: + catalog_id = request.catalog_id + product_retailer_id = request.product_retailer_ids[0] + docs = self.product_indexer.delete(catalog_id, product_retailer_id) + return ProductIndexResponse(catalog_id=catalog_id, documents=docs) + except Exception as e: + logger.error(msg=str(e)) + raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) + + def delete_batch(self, request: ProductDeleteRequest): + try: + docs = self.product_indexer.delete_batch(request.catalog_id, request.product_retailer_ids) + return ProductIndexResponse(catalog_id=request.catalog_id, documents=docs) except Exception as e: logger.error(msg=str(e)) raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) diff --git a/app/indexer/interface.py b/app/indexer/__init__.py similarity index 66% rename from app/indexer/interface.py rename to app/indexer/__init__.py index f0cc939..1081e15 100644 --- a/app/indexer/interface.py +++ b/app/indexer/__init__.py @@ -12,3 +12,11 @@ def index_batch(self): @abstractmethod def search(self): pass + + @abstractmethod + def delete(self): + pass + + @abstractmethod + def delete_batch(self): + pass diff --git a/app/indexer/products.py b/app/indexer/products.py index 5cdae38..9645bf9 100644 --- a/app/indexer/products.py +++ b/app/indexer/products.py @@ -1,99 +1,58 @@ -# from langchain.docstore.document import Document -# from app.handlers.products import Product -# from app.indexer.interface import IDocumentIndexer -# from app.store.interface import IStorage - -# class ProductsIndexer(IDocumentIndexer): -# def __init__(self, storage: IStorage): -# self.storage = storage - -# def index(self, product: Product) -> Document: -# doc = Document( -# page_content=product.title, -# metadata={ -# "id": product.id, -# "title": product.title, -# "org_id": product.org_id, -# "channel_id": product.channel_id, -# "catalog_id": product.catalog_id, -# "product_retailer_id": product.product_retailer_id, -# } -# ) -# self.storage.save(doc) -# return doc - -# def index_batch(self, products: list[Product]) -> list[Document]: -# docs = [] -# for product in products: -# doc = Document( -# page_content=product.title, -# metadata={ -# "id": product.id, -# "title": product.title, -# "org_id": product.org_id, -# "channel_id": product.channel_id, -# "catalog_id": product.catalog_id, -# "product_retailer_id": product.product_retailer_id, -# } -# ) -# docs.append(doc) -# self.storage.save_batch(docs) -# return docs - -# def search(self, search, filter=None, threshold=0.1) -> list[Product]: -# matched_products = self.storage.search(search, filter, threshold) -# products = [] -# for p in matched_products: -# products.append(Product( -# id=p[0].metadata["id"], -# title=p[0].metadata["title"], -# org_id=p[0].metadata["org_id"], -# channel_id=p[0].metadata["channel_id"], -# catalog_id=p[0].metadata["catalog_id"], -# product_retailer_id=p[0].metadata["product_retailer_id"], -# )) -# return products from langchain.docstore.document import Document from app.handlers.products import Product -from app.indexer.interface import IDocumentIndexer -from app.store.interface import IStorage +from app.indexer import IDocumentIndexer +from app.store import IStorage class ProductsIndexer(IDocumentIndexer): def __init__(self, storage: IStorage): self.storage = storage - def _product_to_document(self, product: Product) -> Document: - metadata = { - "id": product.id, - "title": product.title, - "org_id": product.org_id, - "channel_id": product.channel_id, - "catalog_id": product.catalog_id, - "product_retailer_id": product.product_retailer_id, - } - return Document(page_content=product.title, metadata=metadata) - - def index(self, product: Product) -> Document: - doc = self._product_to_document(product) - self.storage.save(doc) - return doc - - def index_batch(self, products: list[Product]) -> list[Document]: - docs = [self._product_to_document(product) for product in products] - self.storage.save_batch(docs) - return docs + def index(self, catalog_id: str, product: Product): + results = self._search_products_by_retailer_id(catalog_id, [product.product_retailer_id]) + ids = [] + if len(results) > 0: + ids = [item["_id"] for item in results] + self.storage.delete(ids=ids) + doc = Document(page_content=product.title, metadata=product) + return self.storage.save(doc) + + def index_batch(self, catalog_id: str, products: list[Product]): + retailer_ids = [product.product_retailer_id for product in products] + results = self._search_products_by_retailer_id(catalog_id, retailer_ids) + ids = [] + if len(results) > 0: + ids = [item["_id"] for item in results] + self.storage.delete(ids=ids) + docs = [Document(page_content=product.title, metadata=product) for product in products] + return self.storage.save_batch(docs) def search(self, search, filter=None, threshold=0.1) -> list[Product]: matched_documents = self.storage.search(search, filter, threshold) products = [ - Product( - id=doc.metadata["id"], - title=doc.metadata["title"], - org_id=doc.metadata["org_id"], - channel_id=doc.metadata["channel_id"], - catalog_id=doc.metadata["catalog_id"], - product_retailer_id=doc.metadata["product_retailer_id"], - ) + Product.from_metadata(doc.metadata) for doc in matched_documents ] - return products \ No newline at end of file + return products + + def _search_products_by_retailer_id(self, catalog_id, ids): + search_filter = { + "metadata.catalog_id": catalog_id, + "metadata.product_retailer_id": ids + } + return self.storage.query_search(search_filter) + + def delete(self, catalog_id, product_retailer_id): + results = self._search_products_by_retailer_id(catalog_id, [product_retailer_id]) + ids = [] + if len(results) > 0: + ids = [item["_id"] for item in results] + self.storage.delete(ids=ids) + return ids + + def delete_batch(self, catalog_id, product_retailer_ids): + results = self._search_products_by_retailer_id(catalog_id, product_retailer_ids) + ids = [] + if len(results) > 0: + ids = [item["_id"] for item in results] + self.storage.delete(ids=ids) + return ids diff --git a/app/main.py b/app/main.py index 1968116..b1be5f6 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,6 @@ -from app.handlers.interface import IDocumentHandler +from app.handlers import IDocumentHandler from app.handlers.products import ProductsHandler -from app.indexer.interface import IDocumentIndexer +from app.indexer import IDocumentIndexer from app.indexer.products import ProductsIndexer from app.store.elasticsearch_vector_store import ElasticsearchVectorStoreIndex from app.config import AppConfig @@ -46,4 +46,3 @@ def __init__(self, config:AppConfig): config = AppConfig() main_app = App(config) -print(config.embedding_type) diff --git a/app/store/__init__.py b/app/store/__init__.py new file mode 100644 index 0000000..4a3e0bf --- /dev/null +++ b/app/store/__init__.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod + +class IStorage(ABC): + @abstractmethod + def save(self) -> list[str]: + pass + + @abstractmethod + def save_batch(self) -> list[str]: + pass + + @abstractmethod + def search(self): + pass + + @abstractmethod + def query_search(self): + pass + + @abstractmethod + def delete(self): + pass + + \ No newline at end of file diff --git a/app/store/elasticsearch_vector_store.py b/app/store/elasticsearch_vector_store.py index bebbd9c..f438c56 100644 --- a/app/store/elasticsearch_vector_store.py +++ b/app/store/elasticsearch_vector_store.py @@ -1,18 +1,18 @@ from langchain.vectorstores import VectorStore from langchain.docstore.document import Document -from app.store.interface import IStorage +from app.store import IStorage class ElasticsearchVectorStoreIndex(IStorage): def __init__(self, vectorstore: VectorStore, score=1.55): self.vectorstore = vectorstore self.score = score - def save(self, doc: Document): - texts = [doc.page_content] + def save(self, doc: Document) -> list[str]: + texts = [doc.page_content] metadatas = [doc.metadata] return self.vectorstore.add_texts(texts, metadatas) - def save_batch(self, documents: list[Document]): + def save_batch(self, documents: list[Document]) -> list[str]: texts = [doc.page_content for doc in documents] metadatas = [doc.metadata for doc in documents] return self.vectorstore.add_texts(texts, metadatas) @@ -20,3 +20,33 @@ def save_batch(self, documents: list[Document]): def search(self, search: str, filter=None, threshold=0.1) -> list[Document]: sr = self.vectorstore.similarity_search_with_score(query=search, k=15, filter=filter) return [doc[0] for doc in sr if doc[1] > threshold] + + def query_search(self, search_filter: dict) -> list[dict]: + match_field: str = list(search_filter.keys())[0] + match_value: str = search_filter[match_field] + term_field = list(search_filter.keys())[1] + term_value = search_filter[term_field] + query_script = { + "bool": { + "must":[ + { "match": {match_field: match_value} } + ], + "filter": { + "terms": { + term_field: term_value + } + } + } + } + try: + self.vectorstore.client.indices.get(index=self.vectorstore.index_name) + except Exception: + return [] + + source = ["metadata"] + response = self.vectorstore.client.search(index=self.vectorstore.index_name, query=query_script, source=source, size=1) + hits = [hit for hit in response["hits"]["hits"]] + return hits + + def delete(self, ids: list[str] = []) -> bool: + return self.vectorstore.delete(ids) diff --git a/app/store/interface.py b/app/store/interface.py deleted file mode 100644 index 51dfc4b..0000000 --- a/app/store/interface.py +++ /dev/null @@ -1,15 +0,0 @@ -from abc import ABC, abstractmethod - -class IStorage(ABC): - @abstractmethod - def save(self): - pass - - @abstractmethod - def save_batch(self): - pass - - @abstractmethod - def search(self): - pass - diff --git a/app/tests/test_config.py b/app/tests/test_config.py index 1fd12c4..2df1b48 100644 --- a/app/tests/test_config.py +++ b/app/tests/test_config.py @@ -34,6 +34,3 @@ def test_config_properties(self): self.assertEqual(config.huggingfacehub["repo_id"], "test_repo_id") self.assertEqual(config.huggingfacehub["task"], "test_task") self.assertEqual(config.huggingfacehub["huggingfacehub_api_token"], "test_token") - -if __name__ == "__main__": - unittest.main() diff --git a/app/tests/test_elasticsearch_vector_store.py b/app/tests/test_elasticsearch_vector_store.py index bdf73ff..de8fb6a 100644 --- a/app/tests/test_elasticsearch_vector_store.py +++ b/app/tests/test_elasticsearch_vector_store.py @@ -1,77 +1,58 @@ -from typing import Any, Iterable, List, Optional, Tuple import unittest -from langchain.vectorstores import VectorStore +from langchain.vectorstores import ElasticVectorSearch from langchain.docstore.document import Document from app.store.elasticsearch_vector_store import ElasticsearchVectorStoreIndex +from unittest.mock import Mock class ElasticsearchVectorStoreIndexTest(unittest.TestCase): def setUp(self): - self.vectorstore = VectorStoreMock() + self.vectorstore = Mock(spec=ElasticVectorSearch) + self.vectorstore.index_name = "index_test" + self.vectorstore.client = Mock() + self.vectorstore.client.indices = Mock() self.storage = ElasticsearchVectorStoreIndex(self.vectorstore) def test_save(self): - doc = Document(page_content="test doc", metadata={"id": "abc123"}) - self.storage.save(doc) - assert len(self.vectorstore.storage) == 1 + self.vectorstore.add_texts.return_value = ["f40a4707-549e-4847-8f48-19b1987b8149"] + doc = Document(page_content="test doc", metadata={"doc_generic_id": "abc123"}) + result = self.storage.save(doc) + self.vectorstore.add_texts.assert_called_once_with([doc.page_content], [doc.metadata]) + self.assertEqual(result, ["f40a4707-549e-4847-8f48-19b1987b8149"]) def test_save_batch(self): + docs_ids = ["1cdd36e4-8e4c-45b7-b3e4-e0365eee0b64", "8dce43f4-f20b-4035-b10d-f42476af4fb2"] + self.vectorstore.add_texts.return_value = docs_ids documents = [ - Document(page_content="first doc", metadata={"id": "abc123"}), - Document(page_content="second doc", metadata={"id": "Jane Doe"}) + Document(page_content="first doc", metadata={"doc_generic_id": "abc123"}), + Document(page_content="second doc", metadata={"doc_generic_id": "abc124"}) ] - self.storage.save_batch(documents) - assert len(self.vectorstore.storage) == 2 - + result = self.storage.save_batch(documents) + self.vectorstore.add_texts.assert_called_once_with( + [doc.page_content for doc in documents], + [doc.metadata for doc in documents], + ) + self.assertEqual(result, docs_ids) def test_search(self): - self.vectorstore.add_texts(["test doc"], [{"id": "abc123"}]) - results = self.storage.search(search="test", filter={"id": "abc123"}) - assert len(results) == 1 - assert results[0].page_content == "test doc" - -if __name__ == "__main__": - unittest.main() - -class VectorStoreMock(VectorStore): - def __init__(self): - self.storage = [] - - def add_texts(self, texts: Iterable[str], metadatas: list[dict] | None = None, **kwargs: Any) -> List[str]: - i = 0 - for text in texts: - doc = Document(page_content=text, metadata=metadatas[i]) - self.storage.append(doc) - i=i+1 - - return texts - - def similarity_search(self, query: str, k: int = 4, **kwargs: Any) -> List[Document]: - return super().similarity_search(query, k, **kwargs) + self.vectorstore.similarity_search_with_score.return_value = [(Document(page_content="test doc", metadata={"doc_generic_id": "abc123"}), 1.6)] + results = self.storage.search(search="test", filter={"doc_generic_id": "abc123"}) + self.vectorstore.similarity_search_with_score.assert_called_once_with(query="test", k=15, filter={"doc_generic_id": "abc123"}) + self.assertEqual(1, len(results)) + self.assertEqual(results[0].page_content, "test doc") - def from_texts( - self, - texts, - embedding, - metadatas = None, - **kwargs: Any, - ): - pass - - def similarity_search_with_score( - self, query: str, k: int = 4, filter: Optional[dict] = None, **kwargs: Any - ) -> List[Tuple[Document, float]]: - - matched_documents = [doc for doc in self.storage if query in doc.page_content.lower()] - - if filter is not None: - matched_documents = self._apply_filter(matched_documents, filter) - - return [(td, 1.6) for td in matched_documents] - - def _apply_filter(self, documents: list[Document], filter: dict) -> list[Document]: - filtered_documents = [] - for doc in documents: - matched = all(doc.metadata.get(k) == v for k, v in filter.items()) - if matched: - filtered_documents.append(doc) - return filtered_documents + def test_delete(self): + self.vectorstore.delete.return_value = True + result = self.storage.delete(ids=["9ff6c70f-1dc4-4d52-b918-8d0d55462a45"]) + self.assertEqual(True, result) + + def test_query_search(self): + self.vectorstore.client.indices.get.return_value = True + mock_search_hits = [{"_id": "53899082-cf01-41d1-ba9b-320a90670755"}] + self.vectorstore.client.search.return_value = {"hits": {"hits": mock_search_hits}} + result = self.storage.query_search({"doc_generic_id": "123", "metadata.sku": ["SKU-123"]}) + self.assertEqual(result, mock_search_hits) + + def test_query_search_with_exception(self): + self.vectorstore.client.indices.get.side_effect = RuntimeError("Index Not Found") + result = self.storage.query_search({"doc_generic_id": "123", "metadata.sku": ["SKU-123"]}) + self.assertEqual(result, []) diff --git a/app/tests/test_products_handler.py b/app/tests/test_products_handler.py index f64c7f6..2433044 100644 --- a/app/tests/test_products_handler.py +++ b/app/tests/test_products_handler.py @@ -1,8 +1,8 @@ import unittest from unittest.mock import Mock -from app.handlers.products import ProductsHandler, Product, ProductSearchRequest, ProductSearchResponse -from app.indexer.interface import IDocumentIndexer -from langchain.docstore.document import Document +from app.handlers.products import ProductsHandler, Product, ProductSearchRequest, ProductSearchResponse, ProductIndexRequest, ProductBatchIndexRequest, ProductIndexResponse, ProductDeleteRequest +from app.indexer import IDocumentIndexer +from fastapi import HTTPException class TestProductsHandler(unittest.TestCase): def setUp(self): @@ -10,76 +10,102 @@ def setUp(self): self.handler = ProductsHandler(self.mock_indexer) def test_index(self): + catalog_id = "789" mock_product = Product( - id="1", title="Test Product", org_id="123", channel_id="456", - catalog_id="789", + catalog_id=catalog_id, product_retailer_id="999" ) - mock_document = Document( - page_content=mock_product.title, - metadata={ - "id": mock_product.id, - "title": mock_product.title, - "org_id": mock_product.org_id, - "channel_id": mock_product.channel_id, - "catalog_id": mock_product.catalog_id, - "product_retailer_id": mock_product.product_retailer_id, - } - ) - self.mock_indexer.index.return_value = mock_document + mock_doc_id = "28ca2dcd-3497-49d5-a1cc-1c3e652fc49c" + self.mock_indexer.index.return_value = [mock_doc_id] + + + mock_product_index_request = ProductIndexRequest(catalog_id=catalog_id, product=mock_product) + result = self.handler.index(mock_product_index_request) - result = self.handler.index(mock_product) + expected_product_index_response = ProductIndexResponse(catalog_id=catalog_id, documents=[mock_doc_id]) + self.assertEqual(result, expected_product_index_response) + self.mock_indexer.index.assert_called_once_with(catalog_id, mock_product) + + def test_index_with_exception(self): + catalog_id = "789" + mock_product = Product( + title="Test Product", + org_id="123", + channel_id="456", + catalog_id=catalog_id, + product_retailer_id="999" + ) + self.mock_indexer.index.side_effect = RuntimeError("some error") + + mock_product_index_request = ProductIndexRequest(catalog_id=catalog_id, product=mock_product) + + with self.assertRaises(HTTPException) as context: + self.handler.index(mock_product_index_request) - self.assertEqual(result, mock_document) - self.mock_indexer.index.assert_called_once_with(mock_product) + self.assertEqual(context.exception.detail[0]["msg"], "some error") def test_batch_index(self): + catalog_id = "789" mock_products = [ Product( - id="1", title="Test Product 1", org_id="123", channel_id="456", - catalog_id="789", - product_retailer_id="999" + catalog_id=catalog_id, + product_retailer_id="998" ), Product( - id="2", title="Test Product 2", org_id="123", channel_id="456", - catalog_id="789", + catalog_id=catalog_id, product_retailer_id="999" ), ] - mock_documents = [ - Document( - page_content=product.title, - metadata={ - "id": product.id, - "title": product.title, - "org_id": product.org_id, - "channel_id": product.channel_id, - "catalog_id": product.catalog_id, - "product_retailer_id": product.product_retailer_id, - } - ) - for product in mock_products + + mock_ids = ["518c3834-e715-4f4d-9976-b4b318698e74", "4b4b9f6f-e177-4709-bd00-429f0675a0c4"] + + self.mock_indexer.index_batch.return_value = mock_ids + + mock_batch_index_request = ProductBatchIndexRequest(catalog_id=catalog_id, products=mock_products) + result = self.handler.batch_index(mock_batch_index_request) + + expected_batch_index_response = ProductIndexResponse(catalog_id=catalog_id, documents=mock_ids) + self.assertEqual(result, expected_batch_index_response) + self.mock_indexer.index_batch.assert_called_once_with(catalog_id, mock_products) + + def test_batch_index_with_exception(self): + catalog_id = "789" + mock_products = [ + Product( + title="Test Product 1", + org_id="123", + channel_id="456", + catalog_id=catalog_id, + product_retailer_id="998" + ), + Product( + title="Test Product 2", + org_id="123", + channel_id="456", + catalog_id=catalog_id, + product_retailer_id="999" + ), ] - self.mock_indexer.index_batch.return_value = mock_documents - result = self.handler.batch_index(mock_products) + self.mock_indexer.index_batch.side_effect = RuntimeError("some error") - self.assertEqual(result, mock_documents) - self.mock_indexer.index_batch.assert_called_once_with(mock_products) + with self.assertRaises(HTTPException) as context: + mock_batch_index_request = ProductBatchIndexRequest(catalog_id=catalog_id, products=mock_products) + self.handler.batch_index(mock_batch_index_request) + self.assertEqual(context.exception.detail[0]["msg"], "some error") def test_search(self): - mock_request = ProductSearchRequest(search="Test", filter={"org_id": "123"}, threshold=0.5) + mock_request = ProductSearchRequest(search="Test", filter={"catalog_id": "789"}, threshold=0.5) mock_product = Product( - id="1", title="Test Product", org_id="123", channel_id="456", @@ -95,5 +121,53 @@ def test_search(self): self.assertEqual(result.products[0], mock_product) self.mock_indexer.search.assert_called_once_with(mock_request.search, mock_request.filter, mock_request.threshold) -if __name__ == "__main__": - unittest.main() + def test_search_with_exception(self): + mock_request = ProductSearchRequest(search="Test", filter={"catalog_id": "789"}, threshold=0.5) + mock_product = Product( + title="Test Product", + org_id="123", + channel_id="456", + catalog_id="789", + product_retailer_id="999" + ) + self.mock_indexer.search.side_effect = RuntimeError("some error") + + with self.assertRaises(HTTPException) as context: + self.handler.search(mock_request) + self.assertEqual(context.exception.detail[0]["msg"], "some error") + + def test_delete(self): + catalog_id = "789" + product_retailer_ids = ["p123"] + mock_id = "4d0c1730-133d-4b49-b407-5141bd571cca" + mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + self.mock_indexer.delete.return_value = [mock_id] + result = self.handler.delete(request=mock_request) + self.assertEqual(result, ProductIndexResponse(catalog_id=catalog_id, documents=[mock_id])) + + def test_delete_with_exception(self): + catalog_id = "789" + product_retailer_ids = ["p123"] + mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + self.mock_indexer.delete.side_effect = RuntimeError("some error") + with self.assertRaises(HTTPException) as context: + self.handler.delete(request=mock_request) + self.assertEqual(context.exception.detail[0]["msg"], "some error") + + def test_delete_batch(self): + catalog_id = "789" + product_retailer_ids = ["p123", "p124"] + mock_ids = ["4d0c1730-133d-4b49-b407-5141bd571cca", "9d063d4b-20f7-4fcd-9751-247c97357efb"] + mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + self.mock_indexer.delete_batch.return_value = mock_ids + result = self.handler.delete_batch(request=mock_request) + self.assertEqual(result, ProductIndexResponse(catalog_id=catalog_id, documents=mock_ids)) + + def test_delete_batch_with_exception(self): + catalog_id = "789" + product_retailer_ids = ["p123", "p124"] + mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + self.mock_indexer.delete_batch.side_effect = RuntimeError("some error") + with self.assertRaises(HTTPException) as context: + self.handler.delete_batch(request=mock_request) + self.assertEqual(context.exception.detail[0]["msg"], "some error") \ No newline at end of file diff --git a/app/tests/test_products_indexer.py b/app/tests/test_products_indexer.py index 22c6d6d..49736c5 100644 --- a/app/tests/test_products_indexer.py +++ b/app/tests/test_products_indexer.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import Mock from langchain.docstore.document import Document -from app.store.interface import IStorage +from app.store import IStorage from app.indexer.products import ProductsIndexer from app.handlers.products import Product @@ -11,78 +11,130 @@ def setUp(self): self.indexer = ProductsIndexer(self.mock_storage) def test_index(self): + catalog_id = "789" mock_product = Product( - id="1", title="Test Product", org_id="123", channel_id="456", - catalog_id="789", + catalog_id=catalog_id, product_retailer_id="999" ) mock_document = Document( page_content=mock_product.title, - metadata={ - "id": mock_product.id, - "title": mock_product.title, - "org_id": mock_product.org_id, - "channel_id": mock_product.channel_id, - "catalog_id": mock_product.catalog_id, - "product_retailer_id": mock_product.product_retailer_id, - } + metadata=mock_product ) + self.mock_storage.save.return_value = mock_document - - result = self.indexer.index(mock_product) - + self.mock_storage.query_search.return_value = [] + + result = self.indexer.index(catalog_id, mock_product) + + self.mock_storage.save.assert_called_once_with(mock_document) + self.mock_storage.query_search.assert_called_once_with({ + "metadata.catalog_id": catalog_id, + "metadata.product_retailer_id": [mock_product.product_retailer_id] + }) self.assertEqual(result, mock_document) + + def test_index_updating(self): + catalog_id = "789" + mock_document_id = "aa01ca00-fd85-457d-a4a1-47b27d9b6121" + mock_product = Product( + title="Test Product", + org_id="123", + channel_id="456", + catalog_id=catalog_id, + product_retailer_id="999" + ) + mock_document = Document( + page_content=mock_product.title, + metadata=mock_product + ) + + self.mock_storage.save.return_value = mock_document + self.mock_storage.query_search.return_value = [{"_id": mock_document_id}] + + result = self.indexer.index(catalog_id, mock_product) + self.mock_storage.save.assert_called_once_with(mock_document) + self.mock_storage.query_search.assert_called_once_with({ + "metadata.catalog_id": catalog_id, + "metadata.product_retailer_id": [mock_product.product_retailer_id] + }) + self.assertEqual(result, mock_document) def test_index_batch(self): + catalog_id = "789" mock_products = [ Product( - id="1", title="Test Product 1", org_id="123", channel_id="456", - catalog_id="789", - product_retailer_id="999" + catalog_id=catalog_id, + product_retailer_id="998" ), Product( - id="2", title="Test Product 2", org_id="123", channel_id="456", - catalog_id="789", + catalog_id=catalog_id, product_retailer_id="999" ), ] mock_documents = [ Document( page_content=product.title, - metadata={ - "id": product.id, - "title": product.title, - "org_id": product.org_id, - "channel_id": product.channel_id, - "catalog_id": product.catalog_id, - "product_retailer_id": product.product_retailer_id, - } + metadata=product ) for product in mock_products ] self.mock_storage.save_batch.return_value = mock_documents + self.mock_storage.query_search.return_value = [] + + result = self.indexer.index_batch(catalog_id ,mock_products) - result = self.indexer.index_batch(mock_products) - + self.mock_storage.save_batch.assert_called_once_with(mock_documents) self.assertEqual(result, mock_documents) + + def test_index_batch_updating(self): + catalog_id = "789" + mock_document_id = "aa01ca00-fd85-457d-a4a1-47b27d9b6121" + mock_products = [ + Product( + title="Test Product 1", + org_id="123", + channel_id="456", + catalog_id=catalog_id, + product_retailer_id="998" + ), + Product( + title="Test Product 2", + org_id="123", + channel_id="456", + catalog_id=catalog_id, + product_retailer_id="999" + ), + ] + mock_documents = [ + Document( + page_content=product.title, + metadata=product + ) + for product in mock_products + ] + self.mock_storage.save_batch.return_value = mock_documents + self.mock_storage.query_search.return_value = [{"_id": mock_document_id}] + + result = self.indexer.index_batch(catalog_id ,mock_products) + self.mock_storage.save_batch.assert_called_once_with(mock_documents) + self.assertEqual(result, mock_documents) def test_search(self): mock_search_query = "Test Query" mock_document = Document( page_content="Document 1", metadata={ - "id": "1", "title": "Title 1", "org_id": "123", "channel_id": "456", @@ -94,9 +146,25 @@ def test_search(self): result = self.indexer.search(mock_search_query) + self.mock_storage.search.assert_called_once_with(mock_search_query, None, 0.1) self.assertEqual(len(result), 1) self.assertEqual(result[0].title, mock_document.metadata["title"]) - self.mock_storage.search.assert_called_once_with(mock_search_query, None, 0.1) -if __name__ == "__main__": - unittest.main() + def test_delete(self): + catalog_id = "789" + product_retailer_id = "pd123" + mock_document_id = "b554c118-b6d7-40a7-8d3e-560d63f91723" + self.mock_storage.query_search.return_value = [{"_id": mock_document_id}] + self.mock_storage.delete.return_value = True + result = self.indexer.delete(catalog_id, product_retailer_id) + self.assertEqual(result, [mock_document_id]) + + def test_delete_batch(self): + catalog_id = "789" + product_retailer_ids = ["pd123", "pd124"] + mock_document_id1 = "b554c118-b6d7-40a7-8d3e-560d63f91723" + mock_document_id2 = "5c69174c-1dbd-4967-b251-d6b9132366b2" + self.mock_storage.query_search.return_value = [{"_id": mock_document_id1}, {"_id": mock_document_id2}] + self.mock_storage.delete.return_value = True + result = self.indexer.delete_batch(catalog_id, product_retailer_ids) + self.assertEqual(result, [mock_document_id1, mock_document_id2]) From 6015f173c9393d222c1c70046301e0c6f53897a6 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Tue, 26 Sep 2023 09:48:05 -0300 Subject: [PATCH 07/11] refactor and run black --- app/config.py | 22 +++- app/handlers/__init__.py | 5 +- app/handlers/products.py | 73 ++++++++---- app/indexer/__init__.py | 7 +- app/indexer/products.py | 25 ++-- app/main.py | 64 +++++----- app/store/__init__.py | 7 +- app/store/elasticsearch_vector_store.py | 23 ++-- app/tests/test_config.py | 5 +- app/tests/test_elasticsearch_vector_store.py | 56 ++++++--- app/tests/test_products_handler.py | 119 ++++++++++++++----- app/tests/test_products_indexer.py | 87 +++++++------- app/util.py | 17 +-- pyproject.toml | 2 +- 14 files changed, 325 insertions(+), 187 deletions(-) diff --git a/app/config.py b/app/config.py index 91cfa96..67b68f5 100644 --- a/app/config.py +++ b/app/config.py @@ -1,16 +1,26 @@ import os + class AppConfig: def __init__(self): - self.product_index_name = os.environ.get("INDEX_PRODUCTS_NAME", "catalog_products") + self.product_index_name = os.environ.get( + "INDEX_PRODUCTS_NAME", "catalog_products" + ) self.es_url = os.environ.get("ELASTICSEARCH_URL", "http://localhost:9200") self.embedding_type = os.environ.get("EMBEDDING_TYPE", "sagemaker") self.sagemaker = { - "endpoint_name": os.environ.get("SAGEMAKER_ENDPOINT_NAME", "huggingface-pytorch-inference-2023-07-28-21-01-20-147"), - "region_name": os.environ.get("SAGEMAKER_REGION_NAME", "us-east-1"), + "endpoint_name": os.environ.get( + "SAGEMAKER_ENDPOINT_NAME", + "huggingface-pytorch-inference-2023-07-28-21-01-20-147", + ), + "region_name": os.environ.get("SAGEMAKER_REGION_NAME", "us-east-1"), } self.huggingfacehub = { - "repo_id": os.environ.get("HUGGINGFACE_REPO_ID", "sentence-transformers/all-MiniLM-L6-v2"), - "task": os.environ.get("HUGGINGFACE_TASK","feature-extraction"), - "huggingfacehub_api_token": os.environ.get("HUGGINGFACE_API_TOKEN" ,"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") + "repo_id": os.environ.get( + "HUGGINGFACE_REPO_ID", "sentence-transformers/all-MiniLM-L6-v2" + ), + "task": os.environ.get("HUGGINGFACE_TASK", "feature-extraction"), + "huggingfacehub_api_token": os.environ.get( + "HUGGINGFACE_API_TOKEN", "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" + ), } diff --git a/app/handlers/__init__.py b/app/handlers/__init__.py index c6e0633..5e65fea 100644 --- a/app/handlers/__init__.py +++ b/app/handlers/__init__.py @@ -1,14 +1,15 @@ from abc import ABC, abstractmethod + class IDocumentHandler(ABC): @abstractmethod def index(self): pass - + @abstractmethod def batch_index(self): pass - + @abstractmethod def search(self): pass diff --git a/app/handlers/products.py b/app/handlers/products.py index 2cdc6a8..d338bc7 100644 --- a/app/handlers/products.py +++ b/app/handlers/products.py @@ -4,60 +4,80 @@ from app.indexer import IDocumentIndexer from pydantic import BaseModel + class Product(BaseModel): + facebook_id: str title: str org_id: str channel_id: str catalog_id: str product_retailer_id: str - + @staticmethod - def from_metadata(metadata:dict): + def from_metadata(metadata: dict): return Product( - title=metadata["title"], - org_id=metadata["org_id"], - channel_id=metadata["channel_id"], - catalog_id=metadata["catalog_id"], - product_retailer_id=metadata["product_retailer_id"], - ) + facebook_id=metadata["facebook_id"], + title=metadata["title"], + org_id=metadata["org_id"], + channel_id=metadata["channel_id"], + catalog_id=metadata["catalog_id"], + product_retailer_id=metadata["product_retailer_id"], + ) + class ProductIndexRequest(BaseModel): catalog_id: str product: Product = None + class ProductBatchIndexRequest(BaseModel): catalog_id: str products: list[Product] = None - + + class ProductIndexResponse(BaseModel): catalog_id: str documents: list[str] + class ProductSearchRequest(BaseModel): search: str filter: dict[str, str] = None threshold: float = 1.5 + class ProductSearchResponse(BaseModel): products: list[Product] + class ProductDeleteRequest(BaseModel): catalog_id: str product_retailer_ids: list[str] + class ProductsHandler(IDocumentHandler): def __init__(self, product_indexer: IDocumentIndexer): self.product_indexer = product_indexer self.router = APIRouter() - self.router.add_api_route("/products/index", endpoint=self.index, methods=["PUT"]) - self.router.add_api_route("/products/batch", endpoint=self.batch_index, methods=["PUT"]) - self.router.add_api_route("/products/search", endpoint=self.search, methods=["GET"]) - self.router.add_api_route("/products/index", endpoint=self.delete, methods=["DELETE"]) - self.router.add_api_route("/products/batch", endpoint=self.delete_batch, methods=["DELETE"]) + self.router.add_api_route( + "/products/index", endpoint=self.index, methods=["PUT"] + ) + self.router.add_api_route( + "/products/batch", endpoint=self.batch_index, methods=["PUT"] + ) + self.router.add_api_route( + "/products/search", endpoint=self.search, methods=["GET"] + ) + self.router.add_api_route( + "/products/index", endpoint=self.delete, methods=["DELETE"] + ) + self.router.add_api_route( + "/products/batch", endpoint=self.delete_batch, methods=["DELETE"] + ) def index(self, request: ProductIndexRequest): try: - docs = self.product_indexer.index(request.catalog_id, request.product) + docs = self.product_indexer.index(request.catalog_id, request.product) return ProductIndexResponse(catalog_id=request.catalog_id, documents=docs) except Exception as e: logger.error(msg=str(e)) @@ -65,7 +85,9 @@ def index(self, request: ProductIndexRequest): def batch_index(self, request: ProductBatchIndexRequest): try: - docs = self.product_indexer.index_batch(request.catalog_id, request.products) + docs = self.product_indexer.index_batch( + request.catalog_id, request.products + ) return ProductIndexResponse(catalog_id=request.catalog_id, documents=docs) except Exception as e: logger.error(msg=str(e)) @@ -73,15 +95,20 @@ def batch_index(self, request: ProductBatchIndexRequest): def search(self, request: ProductSearchRequest): try: - matched_products = self.product_indexer.search(request.search, request.filter, request.threshold) - return ProductSearchResponse( - products=matched_products + matched_products = self.product_indexer.search( + request.search, request.filter, request.threshold ) + return ProductSearchResponse(products=matched_products) except Exception as e: - logger.error(msg=str(e)) + logger.error(msg=str(e)) raise HTTPException(status_code=500, detail=[{"msg": str(e)}]) - def delete(self, catalog_id: str="", product_retailer_id: str="", request: ProductDeleteRequest = None): + def delete( + self, + catalog_id: str = "", + product_retailer_id: str = "", + request: ProductDeleteRequest = None, + ): try: if request is not None: catalog_id = request.catalog_id @@ -94,7 +121,9 @@ def delete(self, catalog_id: str="", product_retailer_id: str="", request: Produ def delete_batch(self, request: ProductDeleteRequest): try: - docs = self.product_indexer.delete_batch(request.catalog_id, request.product_retailer_ids) + docs = self.product_indexer.delete_batch( + request.catalog_id, request.product_retailer_ids + ) return ProductIndexResponse(catalog_id=request.catalog_id, documents=docs) except Exception as e: logger.error(msg=str(e)) diff --git a/app/indexer/__init__.py b/app/indexer/__init__.py index 1081e15..4f4b10b 100644 --- a/app/indexer/__init__.py +++ b/app/indexer/__init__.py @@ -1,18 +1,19 @@ from abc import ABC, abstractmethod + class IDocumentIndexer(ABC): @abstractmethod def index(self): pass - + @abstractmethod def index_batch(self): pass - + @abstractmethod def search(self): pass - + @abstractmethod def delete(self): pass diff --git a/app/indexer/products.py b/app/indexer/products.py index 9645bf9..e0aaf91 100644 --- a/app/indexer/products.py +++ b/app/indexer/products.py @@ -3,12 +3,15 @@ from app.indexer import IDocumentIndexer from app.store import IStorage + class ProductsIndexer(IDocumentIndexer): def __init__(self, storage: IStorage): self.storage = storage def index(self, catalog_id: str, product: Product): - results = self._search_products_by_retailer_id(catalog_id, [product.product_retailer_id]) + results = self._search_products_by_retailer_id( + catalog_id, [product.product_retailer_id] + ) ids = [] if len(results) > 0: ids = [item["_id"] for item in results] @@ -23,26 +26,28 @@ def index_batch(self, catalog_id: str, products: list[Product]): if len(results) > 0: ids = [item["_id"] for item in results] self.storage.delete(ids=ids) - docs = [Document(page_content=product.title, metadata=product) for product in products] + docs = [ + Document(page_content=product.title, metadata=product) + for product in products + ] return self.storage.save_batch(docs) def search(self, search, filter=None, threshold=0.1) -> list[Product]: matched_documents = self.storage.search(search, filter, threshold) - products = [ - Product.from_metadata(doc.metadata) - for doc in matched_documents - ] + products = [Product.from_metadata(doc.metadata) for doc in matched_documents] return products - + def _search_products_by_retailer_id(self, catalog_id, ids): search_filter = { "metadata.catalog_id": catalog_id, - "metadata.product_retailer_id": ids + "metadata.product_retailer_id": ids, } return self.storage.query_search(search_filter) - + def delete(self, catalog_id, product_retailer_id): - results = self._search_products_by_retailer_id(catalog_id, [product_retailer_id]) + results = self._search_products_by_retailer_id( + catalog_id, [product_retailer_id] + ) ids = [] if len(results) > 0: ids = [item["_id"] for item in results] diff --git a/app/main.py b/app/main.py index b1be5f6..c19ae5f 100644 --- a/app/main.py +++ b/app/main.py @@ -11,38 +11,44 @@ from langchain.embeddings.base import Embeddings from langchain.vectorstores import ElasticVectorSearch, VectorStore + class App: + api: FastAPI + config: AppConfig + embeddings: Embeddings + vectorstore: VectorStore + products_handler: IDocumentHandler + products_indexer: IDocumentIndexer + + def __init__(self, config: AppConfig): + self.config = config + if config.embedding_type == "huggingface": + self.embeddings = HuggingFaceHubEmbeddings( + repo_id=config.huggingfacehub["repo_id"], + task=config.huggingfacehub["task"], + huggingfacehub_api_token=config.huggingfacehub[ + "huggingfacehub_api_token" + ], + ) + else: # sagemaker by default + content_handler = ContentHandler() + self.embeddings = SagemakerEndpointEmbeddings( + endpoint_name=config.sagemaker["endpoint_name"], + region_name=config.sagemaker["region_name"], + content_handler=content_handler, + ) - api: FastAPI - config: AppConfig - embeddings: Embeddings - vectorstore: VectorStore - products_handler: IDocumentHandler - products_indexer: IDocumentIndexer + self.api = FastAPI() + self.vectorstore = ElasticVectorSearch( + elasticsearch_url=config.es_url, + index_name=config.product_index_name, + embedding=self.embeddings, + ) + self.elasticStore = ElasticsearchVectorStoreIndex(self.vectorstore) + self.products_indexer = ProductsIndexer(self.elasticStore) + self.products_handler = ProductsHandler(self.products_indexer) + self.api.include_router(self.products_handler.router) - def __init__(self, config:AppConfig): - self.config = config - if config.embedding_type == "huggingface": - self.embeddings = HuggingFaceHubEmbeddings( - repo_id=config.huggingfacehub["repo_id"], - task= config.huggingfacehub["task"], - huggingfacehub_api_token= config.huggingfacehub["huggingfacehub_api_token"], - ) - else: # sagemaker by default - content_handler = ContentHandler() - self.embeddings = SagemakerEndpointEmbeddings( - endpoint_name= config.sagemaker["endpoint_name"], - region_name= config.sagemaker["region_name"], - content_handler=content_handler - ) - - - self.api = FastAPI() - self.vectorstore = ElasticVectorSearch(elasticsearch_url=config.es_url, index_name=config.product_index_name, embedding=self.embeddings) - self.elasticStore = ElasticsearchVectorStoreIndex(self.vectorstore) - self.products_indexer = ProductsIndexer(self.elasticStore) - self.products_handler = ProductsHandler(self.products_indexer) - self.api.include_router(self.products_handler.router) config = AppConfig() main_app = App(config) diff --git a/app/store/__init__.py b/app/store/__init__.py index 4a3e0bf..e7bb16a 100644 --- a/app/store/__init__.py +++ b/app/store/__init__.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod + class IStorage(ABC): @abstractmethod def save(self) -> list[str]: @@ -8,7 +9,7 @@ def save(self) -> list[str]: @abstractmethod def save_batch(self) -> list[str]: pass - + @abstractmethod def search(self): pass @@ -16,9 +17,7 @@ def search(self): @abstractmethod def query_search(self): pass - + @abstractmethod def delete(self): pass - - \ No newline at end of file diff --git a/app/store/elasticsearch_vector_store.py b/app/store/elasticsearch_vector_store.py index f438c56..766660b 100644 --- a/app/store/elasticsearch_vector_store.py +++ b/app/store/elasticsearch_vector_store.py @@ -2,6 +2,7 @@ from langchain.docstore.document import Document from app.store import IStorage + class ElasticsearchVectorStoreIndex(IStorage): def __init__(self, vectorstore: VectorStore, score=1.55): self.vectorstore = vectorstore @@ -18,7 +19,9 @@ def save_batch(self, documents: list[Document]) -> list[str]: return self.vectorstore.add_texts(texts, metadatas) def search(self, search: str, filter=None, threshold=0.1) -> list[Document]: - sr = self.vectorstore.similarity_search_with_score(query=search, k=15, filter=filter) + sr = self.vectorstore.similarity_search_with_score( + query=search, k=15, filter=filter + ) return [doc[0] for doc in sr if doc[1] > threshold] def query_search(self, search_filter: dict) -> list[dict]: @@ -27,16 +30,10 @@ def query_search(self, search_filter: dict) -> list[dict]: term_field = list(search_filter.keys())[1] term_value = search_filter[term_field] query_script = { - "bool": { - "must":[ - { "match": {match_field: match_value} } - ], - "filter": { - "terms": { - term_field: term_value - } - } - } + "bool": { + "must": [{"match": {match_field: match_value}}], + "filter": {"terms": {term_field: term_value}}, + } } try: self.vectorstore.client.indices.get(index=self.vectorstore.index_name) @@ -44,7 +41,9 @@ def query_search(self, search_filter: dict) -> list[dict]: return [] source = ["metadata"] - response = self.vectorstore.client.search(index=self.vectorstore.index_name, query=query_script, source=source, size=1) + response = self.vectorstore.client.search( + index=self.vectorstore.index_name, query=query_script, source=source, size=1 + ) hits = [hit for hit in response["hits"]["hits"]] return hits diff --git a/app/tests/test_config.py b/app/tests/test_config.py index 2df1b48..026a57b 100644 --- a/app/tests/test_config.py +++ b/app/tests/test_config.py @@ -2,6 +2,7 @@ import unittest from app.config import AppConfig + class TestAppConfig(unittest.TestCase): def setUp(self): os.environ["INDEX_PRODUCTS_NAME"] = "test_index" @@ -33,4 +34,6 @@ def test_config_properties(self): self.assertEqual(config.sagemaker["region_name"], "test_region") self.assertEqual(config.huggingfacehub["repo_id"], "test_repo_id") self.assertEqual(config.huggingfacehub["task"], "test_task") - self.assertEqual(config.huggingfacehub["huggingfacehub_api_token"], "test_token") + self.assertEqual( + config.huggingfacehub["huggingfacehub_api_token"], "test_token" + ) diff --git a/app/tests/test_elasticsearch_vector_store.py b/app/tests/test_elasticsearch_vector_store.py index de8fb6a..da9f9ce 100644 --- a/app/tests/test_elasticsearch_vector_store.py +++ b/app/tests/test_elasticsearch_vector_store.py @@ -5,41 +5,61 @@ from app.store.elasticsearch_vector_store import ElasticsearchVectorStoreIndex from unittest.mock import Mock + class ElasticsearchVectorStoreIndexTest(unittest.TestCase): def setUp(self): self.vectorstore = Mock(spec=ElasticVectorSearch) - self.vectorstore.index_name = "index_test" + self.vectorstore.index_name = "index_test" self.vectorstore.client = Mock() self.vectorstore.client.indices = Mock() self.storage = ElasticsearchVectorStoreIndex(self.vectorstore) def test_save(self): - self.vectorstore.add_texts.return_value = ["f40a4707-549e-4847-8f48-19b1987b8149"] + self.vectorstore.add_texts.return_value = [ + "f40a4707-549e-4847-8f48-19b1987b8149" + ] doc = Document(page_content="test doc", metadata={"doc_generic_id": "abc123"}) result = self.storage.save(doc) - self.vectorstore.add_texts.assert_called_once_with([doc.page_content], [doc.metadata]) + self.vectorstore.add_texts.assert_called_once_with( + [doc.page_content], [doc.metadata] + ) self.assertEqual(result, ["f40a4707-549e-4847-8f48-19b1987b8149"]) def test_save_batch(self): - docs_ids = ["1cdd36e4-8e4c-45b7-b3e4-e0365eee0b64", "8dce43f4-f20b-4035-b10d-f42476af4fb2"] + docs_ids = [ + "1cdd36e4-8e4c-45b7-b3e4-e0365eee0b64", + "8dce43f4-f20b-4035-b10d-f42476af4fb2", + ] self.vectorstore.add_texts.return_value = docs_ids documents = [ Document(page_content="first doc", metadata={"doc_generic_id": "abc123"}), - Document(page_content="second doc", metadata={"doc_generic_id": "abc124"}) + Document(page_content="second doc", metadata={"doc_generic_id": "abc124"}), ] result = self.storage.save_batch(documents) self.vectorstore.add_texts.assert_called_once_with( - [doc.page_content for doc in documents], + [doc.page_content for doc in documents], [doc.metadata for doc in documents], ) self.assertEqual(result, docs_ids) + def test_search(self): - self.vectorstore.similarity_search_with_score.return_value = [(Document(page_content="test doc", metadata={"doc_generic_id": "abc123"}), 1.6)] - results = self.storage.search(search="test", filter={"doc_generic_id": "abc123"}) - self.vectorstore.similarity_search_with_score.assert_called_once_with(query="test", k=15, filter={"doc_generic_id": "abc123"}) + self.vectorstore.similarity_search_with_score.return_value = [ + ( + Document( + page_content="test doc", metadata={"doc_generic_id": "abc123"} + ), + 1.6, + ) + ] + results = self.storage.search( + search="test", filter={"doc_generic_id": "abc123"} + ) + self.vectorstore.similarity_search_with_score.assert_called_once_with( + query="test", k=15, filter={"doc_generic_id": "abc123"} + ) self.assertEqual(1, len(results)) self.assertEqual(results[0].page_content, "test doc") - + def test_delete(self): self.vectorstore.delete.return_value = True result = self.storage.delete(ids=["9ff6c70f-1dc4-4d52-b918-8d0d55462a45"]) @@ -48,11 +68,19 @@ def test_delete(self): def test_query_search(self): self.vectorstore.client.indices.get.return_value = True mock_search_hits = [{"_id": "53899082-cf01-41d1-ba9b-320a90670755"}] - self.vectorstore.client.search.return_value = {"hits": {"hits": mock_search_hits}} - result = self.storage.query_search({"doc_generic_id": "123", "metadata.sku": ["SKU-123"]}) + self.vectorstore.client.search.return_value = { + "hits": {"hits": mock_search_hits} + } + result = self.storage.query_search( + {"doc_generic_id": "123", "metadata.sku": ["SKU-123"]} + ) self.assertEqual(result, mock_search_hits) def test_query_search_with_exception(self): - self.vectorstore.client.indices.get.side_effect = RuntimeError("Index Not Found") - result = self.storage.query_search({"doc_generic_id": "123", "metadata.sku": ["SKU-123"]}) + self.vectorstore.client.indices.get.side_effect = RuntimeError( + "Index Not Found" + ) + result = self.storage.query_search( + {"doc_generic_id": "123", "metadata.sku": ["SKU-123"]} + ) self.assertEqual(result, []) diff --git a/app/tests/test_products_handler.py b/app/tests/test_products_handler.py index 2433044..4fe0cf4 100644 --- a/app/tests/test_products_handler.py +++ b/app/tests/test_products_handler.py @@ -1,9 +1,19 @@ import unittest from unittest.mock import Mock -from app.handlers.products import ProductsHandler, Product, ProductSearchRequest, ProductSearchResponse, ProductIndexRequest, ProductBatchIndexRequest, ProductIndexResponse, ProductDeleteRequest +from app.handlers.products import ( + ProductsHandler, + Product, + ProductSearchRequest, + ProductSearchResponse, + ProductIndexRequest, + ProductBatchIndexRequest, + ProductIndexResponse, + ProductDeleteRequest, +) from app.indexer import IDocumentIndexer from fastapi import HTTPException + class TestProductsHandler(unittest.TestCase): def setUp(self): self.mock_indexer = Mock(spec=IDocumentIndexer) @@ -12,36 +22,43 @@ def setUp(self): def test_index(self): catalog_id = "789" mock_product = Product( + facebook_id="123456789", title="Test Product", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" + product_retailer_id="999", ) mock_doc_id = "28ca2dcd-3497-49d5-a1cc-1c3e652fc49c" self.mock_indexer.index.return_value = [mock_doc_id] - - mock_product_index_request = ProductIndexRequest(catalog_id=catalog_id, product=mock_product) + mock_product_index_request = ProductIndexRequest( + catalog_id=catalog_id, product=mock_product + ) result = self.handler.index(mock_product_index_request) - expected_product_index_response = ProductIndexResponse(catalog_id=catalog_id, documents=[mock_doc_id]) + expected_product_index_response = ProductIndexResponse( + catalog_id=catalog_id, documents=[mock_doc_id] + ) self.assertEqual(result, expected_product_index_response) self.mock_indexer.index.assert_called_once_with(catalog_id, mock_product) def test_index_with_exception(self): catalog_id = "789" mock_product = Product( + facebook_id="123456789", title="Test Product", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" + product_retailer_id="999", ) self.mock_indexer.index.side_effect = RuntimeError("some error") - - mock_product_index_request = ProductIndexRequest(catalog_id=catalog_id, product=mock_product) - + + mock_product_index_request = ProductIndexRequest( + catalog_id=catalog_id, product=mock_product + ) + with self.assertRaises(HTTPException) as context: self.handler.index(mock_product_index_request) @@ -51,66 +68,82 @@ def test_batch_index(self): catalog_id = "789" mock_products = [ Product( + facebook_id="1234567891", title="Test Product 1", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="998" + product_retailer_id="998", ), Product( + facebook_id="1234567892", title="Test Product 2", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" + product_retailer_id="999", ), ] - mock_ids = ["518c3834-e715-4f4d-9976-b4b318698e74", "4b4b9f6f-e177-4709-bd00-429f0675a0c4"] - + mock_ids = [ + "518c3834-e715-4f4d-9976-b4b318698e74", + "4b4b9f6f-e177-4709-bd00-429f0675a0c4", + ] + self.mock_indexer.index_batch.return_value = mock_ids - mock_batch_index_request = ProductBatchIndexRequest(catalog_id=catalog_id, products=mock_products) + mock_batch_index_request = ProductBatchIndexRequest( + catalog_id=catalog_id, products=mock_products + ) result = self.handler.batch_index(mock_batch_index_request) - expected_batch_index_response = ProductIndexResponse(catalog_id=catalog_id, documents=mock_ids) + expected_batch_index_response = ProductIndexResponse( + catalog_id=catalog_id, documents=mock_ids + ) self.assertEqual(result, expected_batch_index_response) self.mock_indexer.index_batch.assert_called_once_with(catalog_id, mock_products) - + def test_batch_index_with_exception(self): catalog_id = "789" mock_products = [ Product( + facebook_id="1234567891", title="Test Product 1", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="998" + product_retailer_id="998", ), Product( + facebook_id="1234567892", title="Test Product 2", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" + product_retailer_id="999", ), ] self.mock_indexer.index_batch.side_effect = RuntimeError("some error") with self.assertRaises(HTTPException) as context: - mock_batch_index_request = ProductBatchIndexRequest(catalog_id=catalog_id, products=mock_products) + mock_batch_index_request = ProductBatchIndexRequest( + catalog_id=catalog_id, products=mock_products + ) self.handler.batch_index(mock_batch_index_request) self.assertEqual(context.exception.detail[0]["msg"], "some error") def test_search(self): - mock_request = ProductSearchRequest(search="Test", filter={"catalog_id": "789"}, threshold=0.5) + mock_request = ProductSearchRequest( + search="Test", filter={"catalog_id": "789"}, threshold=0.5 + ) mock_product = Product( + facebook_id="123456789", title="Test Product", org_id="123", channel_id="456", catalog_id="789", - product_retailer_id="999" + product_retailer_id="999", ) self.mock_indexer.search.return_value = [mock_product] @@ -119,16 +152,21 @@ def test_search(self): self.assertIsInstance(result, ProductSearchResponse) self.assertEqual(len(result.products), 1) self.assertEqual(result.products[0], mock_product) - self.mock_indexer.search.assert_called_once_with(mock_request.search, mock_request.filter, mock_request.threshold) + self.mock_indexer.search.assert_called_once_with( + mock_request.search, mock_request.filter, mock_request.threshold + ) def test_search_with_exception(self): - mock_request = ProductSearchRequest(search="Test", filter={"catalog_id": "789"}, threshold=0.5) + mock_request = ProductSearchRequest( + search="Test", filter={"catalog_id": "789"}, threshold=0.5 + ) mock_product = Product( + facebook_id="123456789", title="Test Product", org_id="123", channel_id="456", catalog_id="789", - product_retailer_id="999" + product_retailer_id="999", ) self.mock_indexer.search.side_effect = RuntimeError("some error") @@ -140,15 +178,21 @@ def test_delete(self): catalog_id = "789" product_retailer_ids = ["p123"] mock_id = "4d0c1730-133d-4b49-b407-5141bd571cca" - mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + mock_request = ProductDeleteRequest( + catalog_id=catalog_id, product_retailer_ids=product_retailer_ids + ) self.mock_indexer.delete.return_value = [mock_id] result = self.handler.delete(request=mock_request) - self.assertEqual(result, ProductIndexResponse(catalog_id=catalog_id, documents=[mock_id])) + self.assertEqual( + result, ProductIndexResponse(catalog_id=catalog_id, documents=[mock_id]) + ) def test_delete_with_exception(self): catalog_id = "789" product_retailer_ids = ["p123"] - mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + mock_request = ProductDeleteRequest( + catalog_id=catalog_id, product_retailer_ids=product_retailer_ids + ) self.mock_indexer.delete.side_effect = RuntimeError("some error") with self.assertRaises(HTTPException) as context: self.handler.delete(request=mock_request) @@ -157,17 +201,26 @@ def test_delete_with_exception(self): def test_delete_batch(self): catalog_id = "789" product_retailer_ids = ["p123", "p124"] - mock_ids = ["4d0c1730-133d-4b49-b407-5141bd571cca", "9d063d4b-20f7-4fcd-9751-247c97357efb"] - mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + mock_ids = [ + "4d0c1730-133d-4b49-b407-5141bd571cca", + "9d063d4b-20f7-4fcd-9751-247c97357efb", + ] + mock_request = ProductDeleteRequest( + catalog_id=catalog_id, product_retailer_ids=product_retailer_ids + ) self.mock_indexer.delete_batch.return_value = mock_ids result = self.handler.delete_batch(request=mock_request) - self.assertEqual(result, ProductIndexResponse(catalog_id=catalog_id, documents=mock_ids)) - + self.assertEqual( + result, ProductIndexResponse(catalog_id=catalog_id, documents=mock_ids) + ) + def test_delete_batch_with_exception(self): catalog_id = "789" product_retailer_ids = ["p123", "p124"] - mock_request = ProductDeleteRequest(catalog_id=catalog_id, product_retailer_ids=product_retailer_ids) + mock_request = ProductDeleteRequest( + catalog_id=catalog_id, product_retailer_ids=product_retailer_ids + ) self.mock_indexer.delete_batch.side_effect = RuntimeError("some error") with self.assertRaises(HTTPException) as context: self.handler.delete_batch(request=mock_request) - self.assertEqual(context.exception.detail[0]["msg"], "some error") \ No newline at end of file + self.assertEqual(context.exception.detail[0]["msg"], "some error") diff --git a/app/tests/test_products_indexer.py b/app/tests/test_products_indexer.py index 49736c5..905d695 100644 --- a/app/tests/test_products_indexer.py +++ b/app/tests/test_products_indexer.py @@ -5,6 +5,7 @@ from app.indexer.products import ProductsIndexer from app.handlers.products import Product + class TestProductsIndexer(unittest.TestCase): def setUp(self): self.mock_storage = Mock(spec=IStorage) @@ -13,85 +14,84 @@ def setUp(self): def test_index(self): catalog_id = "789" mock_product = Product( + facebook_id="123456789", title="Test Product", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" - ) - mock_document = Document( - page_content=mock_product.title, - metadata=mock_product + product_retailer_id="999", ) - + mock_document = Document(page_content=mock_product.title, metadata=mock_product) + self.mock_storage.save.return_value = mock_document self.mock_storage.query_search.return_value = [] - + result = self.indexer.index(catalog_id, mock_product) - + self.mock_storage.save.assert_called_once_with(mock_document) - self.mock_storage.query_search.assert_called_once_with({ - "metadata.catalog_id": catalog_id, - "metadata.product_retailer_id": [mock_product.product_retailer_id] - }) + self.mock_storage.query_search.assert_called_once_with( + { + "metadata.catalog_id": catalog_id, + "metadata.product_retailer_id": [mock_product.product_retailer_id], + } + ) self.assertEqual(result, mock_document) def test_index_updating(self): catalog_id = "789" mock_document_id = "aa01ca00-fd85-457d-a4a1-47b27d9b6121" mock_product = Product( + facebook_id="123456789", title="Test Product", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" - ) - mock_document = Document( - page_content=mock_product.title, - metadata=mock_product + product_retailer_id="999", ) - + mock_document = Document(page_content=mock_product.title, metadata=mock_product) + self.mock_storage.save.return_value = mock_document self.mock_storage.query_search.return_value = [{"_id": mock_document_id}] - + result = self.indexer.index(catalog_id, mock_product) - + self.mock_storage.save.assert_called_once_with(mock_document) - self.mock_storage.query_search.assert_called_once_with({ - "metadata.catalog_id": catalog_id, - "metadata.product_retailer_id": [mock_product.product_retailer_id] - }) + self.mock_storage.query_search.assert_called_once_with( + { + "metadata.catalog_id": catalog_id, + "metadata.product_retailer_id": [mock_product.product_retailer_id], + } + ) self.assertEqual(result, mock_document) def test_index_batch(self): catalog_id = "789" mock_products = [ Product( + facebook_id="1234567891", title="Test Product 1", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="998" + product_retailer_id="998", ), Product( + facebook_id="1234567892", title="Test Product 2", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" + product_retailer_id="999", ), ] mock_documents = [ - Document( - page_content=product.title, - metadata=product - ) + Document(page_content=product.title, metadata=product) for product in mock_products ] self.mock_storage.save_batch.return_value = mock_documents self.mock_storage.query_search.return_value = [] - - result = self.indexer.index_batch(catalog_id ,mock_products) + + result = self.indexer.index_batch(catalog_id, mock_products) self.mock_storage.save_batch.assert_called_once_with(mock_documents) self.assertEqual(result, mock_documents) @@ -101,31 +101,30 @@ def test_index_batch_updating(self): mock_document_id = "aa01ca00-fd85-457d-a4a1-47b27d9b6121" mock_products = [ Product( + facebook_id="1234567891", title="Test Product 1", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="998" + product_retailer_id="998", ), Product( + facebook_id="1234567892", title="Test Product 2", org_id="123", channel_id="456", catalog_id=catalog_id, - product_retailer_id="999" + product_retailer_id="999", ), ] mock_documents = [ - Document( - page_content=product.title, - metadata=product - ) + Document(page_content=product.title, metadata=product) for product in mock_products ] self.mock_storage.save_batch.return_value = mock_documents self.mock_storage.query_search.return_value = [{"_id": mock_document_id}] - - result = self.indexer.index_batch(catalog_id ,mock_products) + + result = self.indexer.index_batch(catalog_id, mock_products) self.mock_storage.save_batch.assert_called_once_with(mock_documents) self.assertEqual(result, mock_documents) @@ -135,12 +134,13 @@ def test_search(self): mock_document = Document( page_content="Document 1", metadata={ + "facebook_id": "123456789", "title": "Title 1", "org_id": "123", "channel_id": "456", "catalog_id": "789", "product_retailer_id": "999", - } + }, ) self.mock_storage.search.return_value = [mock_document] @@ -164,7 +164,10 @@ def test_delete_batch(self): product_retailer_ids = ["pd123", "pd124"] mock_document_id1 = "b554c118-b6d7-40a7-8d3e-560d63f91723" mock_document_id2 = "5c69174c-1dbd-4967-b251-d6b9132366b2" - self.mock_storage.query_search.return_value = [{"_id": mock_document_id1}, {"_id": mock_document_id2}] + self.mock_storage.query_search.return_value = [ + {"_id": mock_document_id1}, + {"_id": mock_document_id2}, + ] self.mock_storage.delete.return_value = True result = self.indexer.delete_batch(catalog_id, product_retailer_ids) self.assertEqual(result, [mock_document_id1, mock_document_id2]) diff --git a/app/util.py b/app/util.py index cff5982..53c4b6d 100644 --- a/app/util.py +++ b/app/util.py @@ -1,14 +1,15 @@ import json from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler + class ContentHandler(EmbeddingsContentHandler): - content_type = "application/json" - accepts = "application/json" + content_type = "application/json" + accepts = "application/json" - def transform_input(self, inputs: list[str], model_kwargs: dict) -> bytes: - input_str = json.dumps({"inputs": inputs, **model_kwargs}) - return input_str.encode("utf-8") + def transform_input(self, inputs: list[str], model_kwargs: dict) -> bytes: + input_str = json.dumps({"inputs": inputs, **model_kwargs}) + return input_str.encode("utf-8") - def transform_output(self, output: bytes) -> list[list[float]]: - response_json = json.loads(output.read().decode("utf-8")) - return response_json["vectors"] + def transform_output(self, output: bytes) -> list[list[float]]: + response_json = json.loads(output.read().decode("utf-8")) + return response_json["vectors"] diff --git a/pyproject.toml b/pyproject.toml index 5043170..2c3df6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "microservice-ai" +name = "SentenX" version = "0.1.0" description = "" authors = ["Rafael Soares "] From 5d0ef6f6b89f12a98b0bf1150f4a29bf9bc94a69 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Tue, 26 Sep 2023 09:48:26 -0300 Subject: [PATCH 08/11] update docker compose and readme --- README.md | 123 +++++++++++++++++++++------------------------ docker-compose.yml | 6 ++- 2 files changed, 62 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index e6914e7..4f182c1 100644 --- a/README.md +++ b/README.md @@ -54,29 +54,29 @@ uvicorn app.main:main_app.api --reload request: ```bash -curl -X POST http://localhost:8000/products/index \ +curl -X PUT http://localhost:8000/products/index \ -H 'Content-Type: application/json' \ -d '{ - "id": "1", - "title": "leite em pó 200g", - "org_id": "1", - "channel_id": "5", - "catalog_id": "asdfgh4321", - "product_retailer_id": "abc321" -}' + "catalog_id": "cat1", + "product": { + "facebook_id": "123456789", + "title": "massa para bolo de baunilha", + "org_id": "1", + "channel_id": "5", + "catalog_id": "cat1", + "product_retailer_id": "pp1" + } +} +' ``` response: ```json +status: 200 { - "page_content": "leite em pó 200g", - "metadata": { - "id": "1", - "title": "leite em pó 200g", - "org_id": "1", - "channel_id": "5", - "catalog_id": "asdfgh4321", - "product_retailer_id": "abc321" - } + "catalog_id": "cat1", + "documents": [ + "cac65148-8c1d-423c-a022-2a52cdedcd3c" + ] } ``` @@ -85,54 +85,42 @@ response: request: ```bash -curl -X POST http://localhost:8000/products/batch \ +curl -X PUT http://localhost:8000/products/batch \ -H 'Content-Type: application/json' \ --d '[ - { - "id": "2", - "title": "chocolate em pó 200g", - "org_id": "1", - "channel_id": "5", - "catalog_id": "asdfgh1234", - "product_retailer_id": "abc123" - }, - { - "id": "3", - "title": "café 250g", - "org_id": "1", - "channel_id": "5", - "catalog_id": "zxcvbn5678", - "product_retailer_id": "def456" - } -]' -``` - -response: -```json -[ - { - "page_content": "chocolate em pó 200g", - "metadata": { - "id": "2", - "title": "chocolate em pó 200g", +-d '{ + "catalog_id": "asdfgh", + "products": [ + { + "facebook_id": "1234567891", + "title": "banana prata 1kg", "org_id": "1", "channel_id": "5", - "catalog_id": "asdfgh1234", - "product_retailer_id": "abc123" - } - }, - { - "page_content": "café 250g", - "metadata": { - "id": "3", - "title": "café 250g", + "catalog_id": "asdfgh", + "product_retailer_id": "p1" + }, + { + "facebook_id": "1234567892", + "title": "doce de banana 250g", "org_id": "1", "channel_id": "5", - "catalog_id": "zxcvbn5678", - "product_retailer_id": "def456" + "catalog_id": "asdfgh", + "product_retailer_id": "p2" } - } -] + ] +}' +``` + +response: +```json +status: 200 + +{ + "catalog_id": "asdfgh", + "documents": [ + "f5b8d394-eb62-4c92-9501-51a8ebcf1380", + "bcb551e8-0bd1-4ca7-825b-cf8aa8a3f0e0" + ] +} ``` ### To search for products @@ -142,15 +130,17 @@ request curl http://localhost:8000/products/search \ -H 'Content-Type: application/json' \ -d '{ - "search": "leite em pó", - "filter": { - "channel_id": "5" - }, - "threshold": 1.6 -}' + "search": "massa", + "filter": { + "catalog_id": "cat1" + }, + "threshold": 1.6 +} +' ``` response: ```json +status: 200 { "products": [ { @@ -170,5 +160,6 @@ response: we use unittest with discover to run the tests that are in `./app/tests` ``` -python -m unittest discover ./app/tests +coverage run -m unittest discover -s app/tests ``` + diff --git a/docker-compose.yml b/docker-compose.yml index e39effa..7c28fca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,10 @@ services: - "8000:8000" depends_on: - elasticsearch + environment: + - AWS_SECRET_ACCESS_KEY= + - AWS_ACCESS_KEY_ID= + - ELASTICSEARCH_URL=http://elasticsearch:9200 elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.9.1 ports: @@ -20,7 +24,7 @@ services: - ES_HEAP_SIZE=1g - ES_JAVA_OPTS=-Xms1g -Xmx1g - http.cors.enabled=true - - http.cors.allow-origin=* + - http.cors.allow-origin="*" - http.cors.allow-headers=X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization - http.cors.allow-credentials=true - network.publish_host=localhost From 8fc8cbb6e64cceeb946a7ec3e87f141133fdcf78 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Tue, 26 Sep 2023 10:57:49 -0300 Subject: [PATCH 09/11] update readme with docker compose instructions --- README.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4f182c1..475f9f4 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,10 @@ microservice that uses a sentence transformer model to index and search records. * elasticsearch 8.9.1 ## Quickstart +on root directory of this project run the following commands to: setup sagemaker required keys and elasticsearch url environment variables + ``` export AWS_ACCESS_KEY_ID=YOUR_SAGEMAKER_AWS_ACCESS_KEY export AWS_SECRET_ACCESS_KEY=YOUR_SAGEMAKER_AWS_SECRET_ACCESS_KEY @@ -48,6 +50,26 @@ start the microservice uvicorn app.main:main_app.api --reload ``` +### Docker compose + +to start sentenx with elasticsearch on with docker compose: + +setup `AWS_SECRET_ACCESS_KEY` and `AWS_ACCESS_KEY_ID` on `docker-compose.yml` +``` +docker compose up -d +``` + +to stop: +``` +docker compose down +``` + +to start with rebuild after any change on source: +``` +docker compose up -d -- +``` + + ## Usage ### To index a product @@ -144,13 +166,12 @@ status: 200 { "products": [ { - "id": "1", + "facebook_id": "1", "title": "leite em pó 200g", "org_id": "1", "channel_id": "5", "catalog_id": "asdfgh4321", - "product_retailer_id": "abc321", - "shop": null + "product_retailer_id": "abc321" } ] } From 8f7caf6cdd146a4e9cb8996d09353df06b77309e Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Tue, 26 Sep 2023 10:59:53 -0300 Subject: [PATCH 10/11] add CI --- .github/workflows/ci.yaml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..266bce7 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - '*' + pull_request: + branches: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + - name: Install project dependencies + run: | + pip install poetry + poetry install + working-directory: ${{ github.workspace }} + + - name: Run tests + run: | + poetry run coverage run -m unittest discover ./app/tests/ + poetry run coverage report -m + working-directory: ${{ github.workspace }} + + - name: Upload coverage report + uses: codecov/codecov-action@v2 + From 3c2c71beedef0a9318c9d4e745ea0b796eb67aa2 Mon Sep 17 00:00:00 2001 From: Rafael Soares Date: Wed, 27 Sep 2023 15:19:32 -0300 Subject: [PATCH 11/11] sort imports --- .github/workflows/ci.yaml | 4 ++-- README.md | 8 +++++--- app/handlers/products.py | 3 ++- app/indexer/products.py | 1 + app/main.py | 10 +++++----- app/store/elasticsearch_vector_store.py | 1 + app/util.py | 1 + 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 266bce7..7703e9e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,9 +30,9 @@ jobs: - name: Run tests run: | poetry run coverage run -m unittest discover ./app/tests/ - poetry run coverage report -m + poetry run coverage report + poetry run coverage xml working-directory: ${{ github.workspace }} - name: Upload coverage report uses: codecov/codecov-action@v2 - diff --git a/README.md b/README.md index 475f9f4..f88314e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![CI](https://github.com/weni-ai/SentenX/actions/workflows/ci.yaml/badge.svg)](https://github.com/weni-ai/SentenX/actions/workflows/ci.yaml) + # SentenX microservice that uses a sentence transformer model to index and search records. @@ -52,7 +54,7 @@ uvicorn app.main:main_app.api --reload ### Docker compose -to start sentenx with elasticsearch on with docker compose: +to start sentenx with elasticsearch with docker compose: setup `AWS_SECRET_ACCESS_KEY` and `AWS_ACCESS_KEY_ID` on `docker-compose.yml` ``` @@ -66,7 +68,7 @@ docker compose down to start with rebuild after any change on source: ``` -docker compose up -d -- +docker compose up -d --build ``` @@ -167,7 +169,7 @@ status: 200 "products": [ { "facebook_id": "1", - "title": "leite em pó 200g", + "title": "massa para bolo de baunilha", "org_id": "1", "channel_id": "5", "catalog_id": "asdfgh4321", diff --git a/app/handlers/products.py b/app/handlers/products.py index d338bc7..db57766 100644 --- a/app/handlers/products.py +++ b/app/handlers/products.py @@ -1,8 +1,9 @@ from fastapi import APIRouter, HTTPException from fastapi.logger import logger +from pydantic import BaseModel + from app.handlers import IDocumentHandler from app.indexer import IDocumentIndexer -from pydantic import BaseModel class Product(BaseModel): diff --git a/app/indexer/products.py b/app/indexer/products.py index e0aaf91..fb3dd05 100644 --- a/app/indexer/products.py +++ b/app/indexer/products.py @@ -1,4 +1,5 @@ from langchain.docstore.document import Document + from app.handlers.products import Product from app.indexer import IDocumentIndexer from app.store import IStorage diff --git a/app/main.py b/app/main.py index c19ae5f..5558f05 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,8 @@ +from fastapi import FastAPI +from langchain.embeddings import SagemakerEndpointEmbeddings, HuggingFaceHubEmbeddings +from langchain.embeddings.base import Embeddings +from langchain.vectorstores import ElasticVectorSearch, VectorStore + from app.handlers import IDocumentHandler from app.handlers.products import ProductsHandler from app.indexer import IDocumentIndexer @@ -6,11 +11,6 @@ from app.config import AppConfig from app.util import ContentHandler -from fastapi import FastAPI -from langchain.embeddings import SagemakerEndpointEmbeddings, HuggingFaceHubEmbeddings -from langchain.embeddings.base import Embeddings -from langchain.vectorstores import ElasticVectorSearch, VectorStore - class App: api: FastAPI diff --git a/app/store/elasticsearch_vector_store.py b/app/store/elasticsearch_vector_store.py index 766660b..e1e2038 100644 --- a/app/store/elasticsearch_vector_store.py +++ b/app/store/elasticsearch_vector_store.py @@ -1,5 +1,6 @@ from langchain.vectorstores import VectorStore from langchain.docstore.document import Document + from app.store import IStorage diff --git a/app/util.py b/app/util.py index 53c4b6d..f4be75f 100644 --- a/app/util.py +++ b/app/util.py @@ -1,4 +1,5 @@ import json + from langchain.embeddings.sagemaker_endpoint import EmbeddingsContentHandler