From ca44a201e6d57ddbbe840715ee8a9e7e44838dcf Mon Sep 17 00:00:00 2001 From: tuantran0910 Date: Mon, 2 Dec 2024 09:38:36 +0700 Subject: [PATCH] chore: modify old and remove unused stuff --- chatbot-core/backend/app/models/connector.py | 30 ++++--- chatbot-core/backend/app/models/document.py | 66 +++++++------- chatbot-core/backend/app/routers/base.py | 2 +- .../backend/app/routers/v1/connector.py | 38 +++++--- chatbot-core/backend/app/settings/secrets.py | 6 +- chatbot-core/backend/app/utils/indexing.py | 47 ++++++---- chatbot-core/backend/app/utils/pdf_reader.py | 6 +- chatbot-core/backend/pyproject.toml | 3 +- chatbot-core/backend/uv.lock | 89 +++++++------------ 9 files changed, 144 insertions(+), 143 deletions(-) diff --git a/chatbot-core/backend/app/models/connector.py b/chatbot-core/backend/app/models/connector.py index 2a1ed230..6ff15dbe 100644 --- a/chatbot-core/backend/app/models/connector.py +++ b/chatbot-core/backend/app/models/connector.py @@ -1,8 +1,12 @@ from datetime import datetime from enum import Enum from pydantic import BaseModel, Field -from sqlalchemy import Column, DateTime, Integer, String, JSON -from typing import Optional, List, Dict +from sqlalchemy import DateTime, String +from sqlalchemy import Enum as SQLAlchemyEnum +from sqlalchemy.dialects.mssql import UNIQUEIDENTIFIER +from sqlalchemy.orm import mapped_column, Mapped +from typing import Optional, List +from uuid import uuid4, UUID from app.models import Base @@ -15,13 +19,15 @@ class DocumentSource(str, Enum): class Connector(Base): __tablename__ = "connectors" - id = Column(Integer, primary_key=True, index=True, autoincrement=True) - name = Column(String) - source = Column(String) - connector_specific_config = Column(JSON) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - deleted_at = Column(DateTime, default=None, nullable=True) + id: Mapped[UNIQUEIDENTIFIER] = mapped_column( + UNIQUEIDENTIFIER(as_uuid=True), primary_key=True, index=True, default=uuid4 + ) + name: Mapped[str] = mapped_column(String(255), nullable=False) + source: Mapped[DocumentSource] = mapped_column(SQLAlchemyEnum(DocumentSource, native_enum=False), nullable=False) + connector_specific_config: Mapped[str] = mapped_column(String, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now, onupdate=datetime.now) + deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True, default=None) class ConnectorRequest(BaseModel): @@ -34,13 +40,13 @@ class Config: class ConnectorResponse(BaseModel): - id: int = Field(..., description="Connector ID") + id: UUID = Field(..., description="Connector ID") name: str = Field(..., description="Connector name") source: DocumentSource = Field(..., description="Document source") - connector_specific_config: Optional[Dict] = Field(..., description="Connector specific configuration") + connector_specific_config: Optional[str] = Field(None, description="Connector specific configuration") created_at: datetime = Field(..., description="Created at timestamp") updated_at: datetime = Field(..., description="Updated at timestamp") - deleted_at: Optional[datetime] = Field(..., description="Deleted at timestamp") + deleted_at: Optional[datetime] = Field(None, description="Deleted at timestamp") class Config: from_attributes = True diff --git a/chatbot-core/backend/app/models/document.py b/chatbot-core/backend/app/models/document.py index 38eb46e6..df02f0bb 100644 --- a/chatbot-core/backend/app/models/document.py +++ b/chatbot-core/backend/app/models/document.py @@ -1,8 +1,10 @@ from datetime import datetime from pydantic import BaseModel, Field -from sqlalchemy import Column, DateTime, ForeignKey, Integer, String -from sqlalchemy.orm import relationship -from typing import List +from sqlalchemy import DateTime, ForeignKey, String +from sqlalchemy.dialects.mssql import UNIQUEIDENTIFIER +from sqlalchemy.orm import relationship, mapped_column, Mapped +from typing import List, Optional +from uuid import uuid4 from app.models import Base @@ -10,47 +12,41 @@ class DocumentMetadata(Base): __tablename__ = "document_metadata" - id = Column(Integer, primary_key=True, index=True, autoincrement=True) - name = Column(String, index=True) - description = Column(String) - object_url = Column(String) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - deleted_at = Column(DateTime, default=None, nullable=True) - - tags = relationship( - "DocumentMetadataTags", - back_populates="document_metadata", - cascade="all, delete-orphan", + id: Mapped[UNIQUEIDENTIFIER] = mapped_column( + UNIQUEIDENTIFIER(as_uuid=True), primary_key=True, index=True, default=uuid4 + ) + name: Mapped[str] = mapped_column(String(255), nullable=False) + description: Mapped[str] = mapped_column(String, nullable=True) + document_url: Mapped[str] = mapped_column(String, nullable=False) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now, onupdate=datetime.now) + deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True, default=None) + + tags: Mapped[List["DocumentMetadataTags"]] = relationship( + "DocumentMetadataTags", back_populates="document_metadata", lazy="joined" ) class DocumentMetadataTags(Base): __tablename__ = "document_metadata_tags" - id = Column(Integer, primary_key=True, index=True, autoincrement=True) - name = Column(String, index=True) - document_metadata_id = Column(Integer, ForeignKey("document_metadata.id")) - created_at = Column(DateTime, default=datetime.now) - updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) - deleted_at = Column(DateTime, default=None, nullable=True) - - document_metadata = relationship("DocumentMetadata", back_populates="tags") - - -class DocumentMetadataRequest(BaseModel): - name: str = Field(..., description="Document name") - description: str = Field(..., description="Document description") - tags: List[str] = Field(default_factory=list, description="List of tags") + id: Mapped[UNIQUEIDENTIFIER] = mapped_column( + UNIQUEIDENTIFIER(as_uuid=True), primary_key=True, index=True, default=uuid4 + ) + name: Mapped[str] = mapped_column(String(255), nullable=False) + document_metadata_id: Mapped[UNIQUEIDENTIFIER] = mapped_column( + UNIQUEIDENTIFIER(as_uuid=True), ForeignKey("document_metadata.id"), nullable=False + ) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now, onupdate=datetime.now) + deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True, default=None) - class Config: - from_attributes = True + document_metadata: Mapped[DocumentMetadata] = relationship("DocumentMetadata", back_populates="tags") -class DocumentMetadataResponse(BaseModel): - name: str = Field(..., description="Document name") - description: str = Field(default="", description="Document description") - tags: List[str] = Field(default_factory=list, description="List of tags") +class DocumentUploadResponse(BaseModel): + document_url: str = Field(..., description="Object URL") class Config: from_attributes = True + arbitrary_types_allowed = True diff --git a/chatbot-core/backend/app/routers/base.py b/chatbot-core/backend/app/routers/base.py index c19816e3..453d9f37 100644 --- a/chatbot-core/backend/app/routers/base.py +++ b/chatbot-core/backend/app/routers/base.py @@ -15,7 +15,7 @@ async def home(): BackendAPIResponse() .set_data( { - "org": "Tinh Hoa Solutions", + "organization": "Tinh Hoa Solutions", "description": "API for LLM-based Application Chatbot", } ) diff --git a/chatbot-core/backend/app/routers/v1/connector.py b/chatbot-core/backend/app/routers/v1/connector.py index baaed21c..afeb249e 100644 --- a/chatbot-core/backend/app/routers/v1/connector.py +++ b/chatbot-core/backend/app/routers/v1/connector.py @@ -4,8 +4,8 @@ from app.databases.mssql import get_db_session from app.models.api import APIResponse +from app.models.document import DocumentUploadResponse from app.models.connector import ConnectorRequest, ConnectorResponse -from app.models.document import DocumentMetadataResponse from app.services.connector import ConnectorService from app.services.document import DocumentService from app.settings import Constants @@ -13,12 +13,12 @@ from app.utils.logger import LoggerFactory logger = LoggerFactory().get_logger(__name__) -router = APIRouter(prefix="/connectors", tags=["connectors", "files"]) +router = APIRouter(prefix="/connectors", tags=["connectors", "documents"]) -@router.post("/files/upload", response_model=APIResponse, status_code=status.HTTP_201_CREATED) +@router.post("/documents/upload", response_model=APIResponse, status_code=status.HTTP_201_CREATED) def upload_documents( - files: Annotated[List[UploadFile], File(description="One or multiple documents")], + documents: Annotated[List[UploadFile], File(description="One or multiple documents")], db_session: Session = Depends(get_db_session), ) -> None: """ @@ -29,13 +29,14 @@ def upload_documents( db_session (Session, optional): Database session. Defaults to relational database engine. """ # Upload documents to object storage and trigger indexing pipeline - err = DocumentService(db_session=db_session).upload_documents(files=files) + document_urls, err = DocumentService(db_session=db_session).upload_documents(documents=documents) if err: status_code, detail = err.kind raise HTTPException(status_code=status_code, detail=detail) # Parse response - data = [DocumentMetadataResponse(name=file.filename) for file in files] + data = [DocumentUploadResponse(document_url=document_url) for document_url in document_urls] + return BackendAPIResponse().set_message(message=Constants.API_SUCCESS).set_data(data=data).respond() @@ -60,12 +61,12 @@ def get_connectors(db_session: Session = Depends(get_db_session)) -> None: @router.get("/{connector_id}", response_model=APIResponse, status_code=status.HTTP_200_OK) -def get_connector(connector_id: int, db_session: Session = Depends(get_db_session)) -> None: +def get_connector(connector_id: str, db_session: Session = Depends(get_db_session)) -> None: """ Get connector by id. Args: - connector_id (int): Connector id + connector_id (str): Connector id db_session (Session, optional): Database session. Defaults to relational database engine. """ # Get connector by id @@ -75,7 +76,10 @@ def get_connector(connector_id: int, db_session: Session = Depends(get_db_sessio raise HTTPException(status_code=status_code, detail=detail) # Parse response - data = ConnectorResponse.model_validate(connector) + if connector: + data = ConnectorResponse.model_validate(connector) + else: + data = None return BackendAPIResponse().set_message(message=Constants.API_SUCCESS).set_data(data=data).respond() @@ -95,12 +99,15 @@ def create_connector(connector_request: ConnectorRequest, db_session: Session = status_code, detail = err.kind raise HTTPException(status_code=status_code, detail=detail) - return BackendAPIResponse().set_message(message=Constants.API_SUCCESS).respond() + # Parse response + data = connector_request.model_dump(exclude_unset=True) + + return BackendAPIResponse().set_message(message=Constants.API_SUCCESS).set_data(data=data).respond() @router.patch("/{connector_id}", response_model=APIResponse, status_code=status.HTTP_200_OK) def update_connector( - connector_id: int, connector_request: ConnectorRequest, db_session: Session = Depends(get_db_session) + connector_id: str, connector_request: ConnectorRequest, db_session: Session = Depends(get_db_session) ) -> None: """ Update connector by connector_id. @@ -118,16 +125,19 @@ def update_connector( status_code, detail = err.kind raise HTTPException(status_code=status_code, detail=detail) - return BackendAPIResponse().set_message(message=Constants.API_SUCCESS).respond() + # Parse response + data = connector_request.model_dump(exclude_unset=True) + + return BackendAPIResponse().set_message(message=Constants.API_SUCCESS).set_data(data=data).respond() @router.delete("/{connector_id}", status_code=status.HTTP_204_NO_CONTENT) -def delete_connector(connector_id: int, db_session: Session = Depends(get_db_session)) -> None: +def delete_connector(connector_id: str, db_session: Session = Depends(get_db_session)) -> None: """ Delete connector by connector_id. Args: - connector_id (int): Connector id + connector_id (str): Connector id db_session (Session, optional): Database session. Defaults to relational database engine. """ # Delete connector by id diff --git a/chatbot-core/backend/app/settings/secrets.py b/chatbot-core/backend/app/settings/secrets.py index 61dab926..a9034b30 100644 --- a/chatbot-core/backend/app/settings/secrets.py +++ b/chatbot-core/backend/app/settings/secrets.py @@ -9,12 +9,12 @@ class Secrets: MSSQL_HOST = os.getenv("MSSQL_HOST", "127.0.0.1") MSSQL_USER = os.getenv("MSSQL_USER", "SA") MSSQL_SA_PASSWORD = os.getenv("MSSQL_SA_PASSWORD", "P&ssword123") - MSSQL_DB = os.getenv("MSSQL_DB", "ezhr_chatbot") + MSSQL_DB = os.getenv("MSSQL_DB", "chatbot_core") # Minio Credentials MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "127.0.0.1:9000") - MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "minioadmin") - MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minioadmin") + MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "S3User") + MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "P&ssword123") # Qdrant Credentials QDRANT_HOST = os.getenv("QDRANT_HOST", "localhost") diff --git a/chatbot-core/backend/app/utils/indexing.py b/chatbot-core/backend/app/utils/indexing.py index d6569604..45b3eeaf 100644 --- a/chatbot-core/backend/app/utils/indexing.py +++ b/chatbot-core/backend/app/utils/indexing.py @@ -1,5 +1,6 @@ from fastapi import File, UploadFile from llama_index.core import Settings +from llama_index.core.storage.storage_context import StorageContext from llama_index.core.extractors import KeywordExtractor, QuestionsAnsweredExtractor from llama_index.core.ingestion import IngestionCache, IngestionPipeline from llama_index.core.node_parser import SemanticSplitterNodeParser @@ -7,8 +8,8 @@ from llama_index.vector_stores.qdrant import QdrantVectorStore from typing import Annotated, Any, List -from app.databases.qdrant import get_vector_db_client -from app.databases.redis import get_cache_store_client +from app.databases.qdrant import get_vector_db_connector +from app.databases.redis import get_cache_connector from app.settings import Constants from app.utils.pdf_reader import parse_pdf @@ -16,6 +17,9 @@ def get_transformations() -> List[Any]: """ Get the transformation components for the ingestion pipeline + + Returns: + List[Any]: List of LlamaIndex transformation components """ # Define node postprocessor methods extractors = [ @@ -36,24 +40,37 @@ def get_transformations() -> List[Any]: def index_document_to_vector_db( - file: Annotated[UploadFile, File(description="PDF file")], + document: Annotated[UploadFile, File(description="PDF file")], ) -> None: + """ + Index a PDF document into the vector database. + + Args: + document (UploadFile): PDF file + """ # Parse PDF file into LlamaIndex Document objects - documents = parse_pdf(file) + documents = parse_pdf(document=document) - # Define vector store - vector_db_client = get_vector_db_client() - vector_store = QdrantVectorStore( - client=vector_db_client, - collection_name=Constants.LLM_QDRANT_COLLECTION, - ) + # Initialize the vector store for the ingestion pipeline + with get_vector_db_connector() as vector_db_connector: + # Create a collection in the vector database + vector_db_connector.create_collection( + collection_name=Constants.LLM_QDRANT_COLLECTION, + ) + + vector_db_client = vector_db_connector.client + vector_store = QdrantVectorStore( + client=vector_db_client, + collection_name=Constants.LLM_QDRANT_COLLECTION, + ) # Initialize the cache store for the ingestion pipeline - cache = get_cache_store_client() - ingest_cache = IngestionCache( - cache=cache, - collection=Constants.LLM_REDIS_CACHE_COLLECTION, - ) + with get_cache_connector() as cache_connector: + cache_store = cache_connector.get_cache_store() + ingest_cache = IngestionCache( + cache=cache_store, + collection=Constants.LLM_REDIS_CACHE_COLLECTION, + ) # Define transformation components (chunking + node postprocessors) transformations = get_transformations() diff --git a/chatbot-core/backend/app/utils/pdf_reader.py b/chatbot-core/backend/app/utils/pdf_reader.py index 1176d3fb..452ca247 100644 --- a/chatbot-core/backend/app/utils/pdf_reader.py +++ b/chatbot-core/backend/app/utils/pdf_reader.py @@ -9,20 +9,20 @@ def parse_pdf( - file: Annotated[UploadFile, File(description="PDF file")], + document: Annotated[UploadFile, File(description="PDF file")], ) -> List[Document] | None: """ Parse a PDF file into Llamaindex Document objects. Args: - file (UploadFile): PDF file to parse. + document (UploadFile): PDF file to parse. Returns: List[Document]: List of Llamaindex Document objects. """ try: documents = [] - with pdfplumber.open(file.file) as pdf: + with pdfplumber.open(document.file) as pdf: for page in pdf.pages: documents.append(Document(text=page.extract_text())) except Exception as e: diff --git a/chatbot-core/backend/pyproject.toml b/chatbot-core/backend/pyproject.toml index cbe953e3..a8879d40 100644 --- a/chatbot-core/backend/pyproject.toml +++ b/chatbot-core/backend/pyproject.toml @@ -30,9 +30,8 @@ dependencies = [ "pdfplumber>=0.11.4", "qdrant-client>=1.12.1", "minio>=7.2.12", - "sqlalchemy>=1.4.54", - "pymssql>=2.3.2", "pyodbc>=5.2.0", + "sqlalchemy>=2.0.36", ] [project.optional-dependencies] diff --git a/chatbot-core/backend/uv.lock b/chatbot-core/backend/uv.lock index af2d2e30..cee3e078 100644 --- a/chatbot-core/backend/uv.lock +++ b/chatbot-core/backend/uv.lock @@ -202,7 +202,6 @@ dependencies = [ { name = "llama-index-vector-stores-qdrant" }, { name = "minio" }, { name = "pdfplumber" }, - { name = "pymssql" }, { name = "pyodbc" }, { name = "qdrant-client" }, { name = "redis" }, @@ -243,14 +242,13 @@ requires-dist = [ { name = "llama-index-vector-stores-qdrant", specifier = ">=0.4.0" }, { name = "minio", specifier = ">=7.2.12" }, { name = "pdfplumber", specifier = ">=0.11.4" }, - { name = "pymssql", specifier = ">=2.3.2" }, { name = "pyodbc", specifier = ">=5.2.0" }, { name = "pyright", marker = "extra == 'dev'" }, { name = "pytest", marker = "extra == 'test'" }, { name = "qdrant-client", specifier = ">=1.12.1" }, { name = "redis", specifier = ">=5.2.0" }, { name = "ruff", marker = "extra == 'dev'" }, - { name = "sqlalchemy", specifier = ">=1.4.54" }, + { name = "sqlalchemy", specifier = ">=2.0.36" }, { name = "uvicorn", specifier = "==0.23.2" }, ] @@ -2378,47 +2376,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, ] -[[package]] -name = "pymssql" -version = "2.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/c8/2ce5b171581c2e4d5d9726aaa805eb01febc7ed70a3bf686e1e0f5501b07/pymssql-2.3.2.tar.gz", hash = "sha256:18089641b687be1ebd0f64f0d1ff977478a397ffa1af372bdf10dbec29cf6d2e", size = 184760 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/0d/a919acf75a26a5c5dabceb11b4f7446d7860a761ef68bdce3cd1055c9d25/pymssql-2.3.2-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:2a44a0898dacba4e25cac8778d0ed112e297883fe862204e447081888da78dc4", size = 3070057 }, - { url = "https://files.pythonhosted.org/packages/e8/e0/3a87b214403c361a19bd6c7d8462a6f3a1e87661909fc326b8f5f0efd9f8/pymssql-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9737c06b13ca2012b9900185fa3af72a37941c532da2e6373dd7c9ab16abddf", size = 4044744 }, - { url = "https://files.pythonhosted.org/packages/1b/f0/0359b8a371723d8e3a9255755e42fcb3ab32700a4a14b3121dbc438ad39f/pymssql-2.3.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0831c5c95aab0b9aba5142dc97e28f59c4130e1c34ffc13ecbfdd4d2fe45b8a0", size = 4032859 }, - { url = "https://files.pythonhosted.org/packages/06/d6/3499b98a591bf713deed6f48b1b3b3e80c008b4ed1760d6f9c07f7824772/pymssql-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae02cc1594f0addd748bf5ac1ccc7a73c03846ada9c553663c381b242b586606", size = 4391018 }, - { url = "https://files.pythonhosted.org/packages/9f/cb/d8aadb2628917b2fc386446f871dc32124c5029c9e48f6dea4d887dba70c/pymssql-2.3.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1c99dba4bf5b1ce10657e9e2885f18ba9179190251b63d1498e7d6d72e64f1ce", size = 4788674 }, - { url = "https://files.pythonhosted.org/packages/2f/a0/d80b9ad5807f5a14e249f011a6d24f16fa6ef96bd6e643d9b677d74d90a0/pymssql-2.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9e3d6fada7fbe7a5f5fafc420673f777bab3f399c78fa44e29de6a8cbc36e515", size = 4123868 }, - { url = "https://files.pythonhosted.org/packages/b9/87/5247858d1a7d03634c2082679c1a4fe40775e226fb3fc70c855851fe9938/pymssql-2.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5904d78e61668ec89761d3ae01efd4b42b31d820f612929f449e93cd23ba3c54", size = 4157236 }, - { url = "https://files.pythonhosted.org/packages/3d/29/07da1e426b9627a870e762ec2d1b7f5fc144d4c201a312cec79633486cb0/pymssql-2.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9361593a89c9162fc631baf648a87e2666373382d9d54aacfb19edab9ceb2007", size = 4629804 }, - { url = "https://files.pythonhosted.org/packages/36/9b/1ced1ab60e5b9e025aab65bede8f05595e1c763db1decd20c093f8267176/pymssql-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0768d90f96ae3267d7561d3bcfe94dd671d107489e870388b12570c3debbc552", size = 4843590 }, - { url = "https://files.pythonhosted.org/packages/a4/9e/94af63f23becb5b411eba30d2090f17b8455c91166209e3c672d3199e859/pymssql-2.3.2-cp311-cp311-win32.whl", hash = "sha256:97fbd8491ad3ece0adcb321acec6db48b8fe37bc74af4c91bb657d4d9347d634", size = 1319041 }, - { url = "https://files.pythonhosted.org/packages/65/f8/9336690fb988f7a848aaafd0b1df9aff9e16b7c24be1da7fc27e64e0b30c/pymssql-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:79cdc3ed1da3129ba56232127db86279728c4328595e2532ed4d0da6379a5c72", size = 2005840 }, - { url = "https://files.pythonhosted.org/packages/03/b4/d9b30b565cf8af6d3f0e90802694860ff2e1c269d444be6ca24c4cfd9761/pymssql-2.3.2-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:235c230e56d8c8c5f289e665c538f31d967fec302d05ad269dcd64fa9d6eb3b7", size = 3042870 }, - { url = "https://files.pythonhosted.org/packages/76/da/be4296cf0b4fd8b4f1a082cba2b8d08d7e730e98b8f0be62c84db891796f/pymssql-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bc33ed9af6d8ebea2d49144cd2317b7ae1105dd51dddfd46982c90c8f0cf6ab", size = 3987883 }, - { url = "https://files.pythonhosted.org/packages/9e/80/ae1a77e5de1ca0a9f0a1ff5d9b60dc9c270e3afa6932302e459bd529aadc/pymssql-2.3.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:793a93da1521fa66bf02b3b873065e22bf14bda5570e005ce3d5fae0776d7b92", size = 3961886 }, - { url = "https://files.pythonhosted.org/packages/38/d3/28e827a01234853fcfbb71703a5dcee490988eb5d1ff8859ac9fcc6db38c/pymssql-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b156b15165f7a0bbb392a124d8e2d678145c93e5bfcfef3b637e4d87eadcc85b", size = 4350551 }, - { url = "https://files.pythonhosted.org/packages/30/53/626d5f203d3d05e6af5cfd1c611def622abb815ba7315c766c4faefd569d/pymssql-2.3.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f2b1da4e68d618c7972e583ae19f386ae620258acb61564e8067c203f27cd769", size = 4745384 }, - { url = "https://files.pythonhosted.org/packages/67/ec/ff4d831bd250b2b5491c7f85abf04ce2c5613cd955e1855957b98fd72b89/pymssql-2.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2f4093b95f1f3a1232687fc92f652aaf675eb423db8549c16d146b91ac2f0eba", size = 4045126 }, - { url = "https://files.pythonhosted.org/packages/85/ed/79ec7edbd5a99e445d85a46b48ea71ae9a920c9e92b743318446f4d4dffb/pymssql-2.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cc13c2e0f1b8efc3a46941de9db768fa59937b5a54081ec0cb0ff0da17d1fff3", size = 4107959 }, - { url = "https://files.pythonhosted.org/packages/71/27/aff4b90fcfdfb3227f881d9ca6665139adbf1c397106e0f588493156e449/pymssql-2.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6019d2939963112662288704f608f31634038bffcfd5cad1bc79cb167edb3cc1", size = 4566181 }, - { url = "https://files.pythonhosted.org/packages/9b/eb/376e2ae6ba7c7632137b9f46318573d0a988fc32184aea68eee64dc78d7a/pymssql-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41d09e1b2534229b288c37b88c1de3d964317af2c7eec58bfb97e01d679eba27", size = 4787575 }, - { url = "https://files.pythonhosted.org/packages/6e/5b/fa906b132431009174bb966c7b7ce0da3dbd9343dc6e1ed6c448b22a4291/pymssql-2.3.2-cp312-cp312-win32.whl", hash = "sha256:b16d5880f7028442d6c49c94801ce9bff3af8af0fbda7c6039febb936714aed5", size = 1306859 }, - { url = "https://files.pythonhosted.org/packages/11/2e/be51090e0c1c99b9259d06d2e3533c0e3681fd95203fc50040e6c18685a5/pymssql-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:a3f9e7eb813dfeab6d01bf6474049bb76b0521235159d3e969ec82df384eac49", size = 1990007 }, - { url = "https://files.pythonhosted.org/packages/e0/26/f90c0251c0452fb6a80c44a7d7eb9b1e63e1657098659364ec81cb9cbb87/pymssql-2.3.2-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:f282e701dca155b3e7f1644d7e3b60c201ca5f3be8045bce34042d3c737d63ee", size = 3031958 }, - { url = "https://files.pythonhosted.org/packages/ea/8d/8146de09a00a3c1737c1f1feb83a10519a406da045b3e7f5ad315d2266fd/pymssql-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1791f4627c42fe2d2833c884d036b0c5c8cf628f2cdfa3536191c217acf729e", size = 3981704 }, - { url = "https://files.pythonhosted.org/packages/97/75/b1e7586c73e63f35664cf4dcf8df79d18892a3a57db3e93039443fb5a568/pymssql-2.3.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3870085a49e5332bc67ecb24f217c586977d5352eb51598244fc7bc278eee3e1", size = 3964863 }, - { url = "https://files.pythonhosted.org/packages/40/5c/a1e6bbb17c5a606eeba78da8f13784c5afa6e614c9a44348a95c229fbb0e/pymssql-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1afda7b7022eff9451bd83e3f64c450a1a8cdff4ba8b8e399866dcd2cb861a1e", size = 4346193 }, - { url = "https://files.pythonhosted.org/packages/ca/5f/ec35ac1efa66172c626a0e86cc1520d2964b415fae6f2a7a818ef1d98fcc/pymssql-2.3.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b0c2b11aca16617cacaf385fb94134e73ba0216a924f9b85778cc7e3d3713361", size = 4743947 }, - { url = "https://files.pythonhosted.org/packages/1c/fa/9e1d88e2f025ce8d389f861bd962c0558ee23bc1b6d18981a967b6b51e6d/pymssql-2.3.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:2568944db3888996e161b40ad06c1b9e0fbb6cfcb38279a3abb98ece7a8e1c4a", size = 4047878 }, - { url = "https://files.pythonhosted.org/packages/f5/2a/7ad8a39d8ff79a8f7ee7fc5a9c43f22cd365aff3f296b20a702c164eebb6/pymssql-2.3.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ee8ee2c7c227c413ad9b88ddba1cb6a25e28c217ae73ecac1c7a6b8c29003604", size = 4109700 }, - { url = "https://files.pythonhosted.org/packages/b6/94/eed7fff479be51827e03c2bfcffda73dfe4e0d72c4c8144425aa63daede0/pymssql-2.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8cd806380d362d4cef2d925a6baee6a4b2b151a92cac2cab5c4bfabed4be4849", size = 4565816 }, - { url = "https://files.pythonhosted.org/packages/f1/a1/f99f37547126981a351e0c8854f35b7d984238c68af54ff8863ea2d3644b/pymssql-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ef0d29c705db552f9e75230f946b0ca9db0db903c5c9ee79ce8b88ad25ea9670", size = 4786896 }, - { url = "https://files.pythonhosted.org/packages/24/4f/93438cd488497f1c089d077380c3bc9a7adf98666fa01d7a380861440965/pymssql-2.3.2-cp313-cp313-win32.whl", hash = "sha256:1037053e6c74d6fe14c428cc942968b4e4bf06854706a83fe8e822e475e3f107", size = 1306239 }, - { url = "https://files.pythonhosted.org/packages/ad/b9/6782fee30a1bb699aa023e132ca85d137e20466ef9fe562656a1e3dec25b/pymssql-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:148b7714fff5a5b7ce038e92b02dd9bf68fe442c181a3aae32148e7b13f6db95", size = 1988634 }, -] - [[package]] name = "pyodbc" version = "5.2.0" @@ -2898,23 +2855,39 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "1.4.54" +version = "2.0.36" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "greenlet", marker = "(python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/af/20290b55d469e873cba9d41c0206ab5461ff49d759989b3fe65010f9d265/sqlalchemy-1.4.54.tar.gz", hash = "sha256:4470fbed088c35dc20b78a39aaf4ae54fe81790c783b3264872a0224f437c31a", size = 8470350 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/49/fb98983b5568e93696a25fd5bec1b789095b79a72d5f57c6effddaa81d0a/SQLAlchemy-1.4.54-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b05e0626ec1c391432eabb47a8abd3bf199fb74bfde7cc44a26d2b1b352c2c6e", size = 1589301 }, - { url = "https://files.pythonhosted.org/packages/03/98/5a81430bbd646991346cb088a2bdc84d1bcd3dbe6b0cfc1aaa898370e5c7/SQLAlchemy-1.4.54-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13e91d6892b5fcb94a36ba061fb7a1f03d0185ed9d8a77c84ba389e5bb05e936", size = 1629553 }, - { url = "https://files.pythonhosted.org/packages/f1/17/14e35db2b0d6deaa27691d014addbb0dd6f7e044f7ee465446a3c0c71404/SQLAlchemy-1.4.54-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb59a11689ff3c58e7652260127f9e34f7f45478a2f3ef831ab6db7bcd72108f", size = 1627640 }, - { url = "https://files.pythonhosted.org/packages/98/62/335006a8f2c98f704f391e1a0cc01446d1b1b9c198f579f03599f55bd860/SQLAlchemy-1.4.54-cp311-cp311-win32.whl", hash = "sha256:1390ca2d301a2708fd4425c6d75528d22f26b8f5cbc9faba1ddca136671432bc", size = 1591723 }, - { url = "https://files.pythonhosted.org/packages/e2/a1/6b4b8c07082920f5445ec65c221fa33baab102aced5dcc2d87a15d3f8db4/SQLAlchemy-1.4.54-cp311-cp311-win_amd64.whl", hash = "sha256:2b37931eac4b837c45e2522066bda221ac6d80e78922fb77c75eb12e4dbcdee5", size = 1593511 }, - { url = "https://files.pythonhosted.org/packages/a5/1b/aa9b99be95d1615f058b5827447c18505b7b3f1dfcbd6ce1b331c2107152/SQLAlchemy-1.4.54-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3f01c2629a7d6b30d8afe0326b8c649b74825a0e1ebdcb01e8ffd1c920deb07d", size = 1589983 }, - { url = "https://files.pythonhosted.org/packages/59/47/cb0fc64e5344f0a3d02216796c342525ab283f8f052d1c31a1d487d08aa0/SQLAlchemy-1.4.54-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c24dd161c06992ed16c5e528a75878edbaeced5660c3db88c820f1f0d3fe1f4", size = 1630158 }, - { url = "https://files.pythonhosted.org/packages/c0/8b/f45dd378f6c97e8ff9332ff3d03ecb0b8c491be5bb7a698783b5a2f358ec/SQLAlchemy-1.4.54-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e0d47d619c739bdc636bbe007da4519fc953393304a5943e0b5aec96c9877c", size = 1629232 }, - { url = "https://files.pythonhosted.org/packages/0d/3c/884fe389f5bec86a310b81e79abaa1e26e5d78dc10a84d544a6822833e47/SQLAlchemy-1.4.54-cp312-cp312-win32.whl", hash = "sha256:12bc0141b245918b80d9d17eca94663dbd3f5266ac77a0be60750f36102bbb0f", size = 1592027 }, - { url = "https://files.pythonhosted.org/packages/01/c3/c690d037be57efd3a69cde16a2ef1bd2a905dafe869434d33836de0983d0/SQLAlchemy-1.4.54-cp312-cp312-win_amd64.whl", hash = "sha256:f941aaf15f47f316123e1933f9ea91a6efda73a161a6ab6046d1cde37be62c88", size = 1593827 }, +sdist = { url = "https://files.pythonhosted.org/packages/50/65/9cbc9c4c3287bed2499e05033e207473504dc4df999ce49385fb1f8b058a/sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", size = 9574485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/4e/5a67963fd7cbc1beb8bd2152e907419f4c940ef04600b10151a751fe9e06/SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c", size = 2093782 }, + { url = "https://files.pythonhosted.org/packages/b3/24/30e33b6389ebb5a17df2a4243b091bc709fb3dfc9a48c8d72f8e037c943d/SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71", size = 2084180 }, + { url = "https://files.pythonhosted.org/packages/10/1e/70e9ed2143a27065246be40f78637ad5160ea0f5fd32f8cab819a31ff54d/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff", size = 3202469 }, + { url = "https://files.pythonhosted.org/packages/b4/5f/95e0ed74093ac3c0db6acfa944d4d8ac6284ef5e1136b878a327ea1f975a/SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d", size = 3202464 }, + { url = "https://files.pythonhosted.org/packages/91/95/2cf9b85a6bc2ee660e40594dffe04e777e7b8617fd0c6d77a0f782ea96c9/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb", size = 3139508 }, + { url = "https://files.pythonhosted.org/packages/92/ea/f0c01bc646456e4345c0fb5a3ddef457326285c2dc60435b0eb96b61bf31/SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8", size = 3159837 }, + { url = "https://files.pythonhosted.org/packages/a6/93/c8edbf153ee38fe529773240877bf1332ed95328aceef6254288f446994e/SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f", size = 2064529 }, + { url = "https://files.pythonhosted.org/packages/b1/03/d12b7c1d36fd80150c1d52e121614cf9377dac99e5497af8d8f5b2a8db64/SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959", size = 2089874 }, + { url = "https://files.pythonhosted.org/packages/b8/bf/005dc47f0e57556e14512d5542f3f183b94fde46e15ff1588ec58ca89555/SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", size = 2092378 }, + { url = "https://files.pythonhosted.org/packages/94/65/f109d5720779a08e6e324ec89a744f5f92c48bd8005edc814bf72fbb24e5/SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", size = 2082778 }, + { url = "https://files.pythonhosted.org/packages/60/f6/d9aa8c49c44f9b8c9b9dada1f12fa78df3d4c42aa2de437164b83ee1123c/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53", size = 3232191 }, + { url = "https://files.pythonhosted.org/packages/8a/ab/81d4514527c068670cb1d7ab62a81a185df53a7c379bd2a5636e83d09ede/SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", size = 3243044 }, + { url = "https://files.pythonhosted.org/packages/35/b4/f87c014ecf5167dc669199cafdb20a7358ff4b1d49ce3622cc48571f811c/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", size = 3178511 }, + { url = "https://files.pythonhosted.org/packages/ea/09/badfc9293bc3ccba6ede05e5f2b44a760aa47d84da1fc5a326e963e3d4d9/SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", size = 3205147 }, + { url = "https://files.pythonhosted.org/packages/c8/60/70e681de02a13c4b27979b7b78da3058c49bacc9858c89ba672e030f03f2/SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", size = 2062709 }, + { url = "https://files.pythonhosted.org/packages/b7/ed/f6cd9395e41bfe47dd253d74d2dfc3cab34980d4e20c8878cb1117306085/SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", size = 2088433 }, + { url = "https://files.pythonhosted.org/packages/78/5c/236398ae3678b3237726819b484f15f5c038a9549da01703a771f05a00d6/SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", size = 2087651 }, + { url = "https://files.pythonhosted.org/packages/a8/14/55c47420c0d23fb67a35af8be4719199b81c59f3084c28d131a7767b0b0b/SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", size = 2078132 }, + { url = "https://files.pythonhosted.org/packages/3d/97/1e843b36abff8c4a7aa2e37f9bea364f90d021754c2de94d792c2d91405b/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", size = 3164559 }, + { url = "https://files.pythonhosted.org/packages/7b/c5/07f18a897b997f6d6b234fab2bf31dccf66d5d16a79fe329aefc95cd7461/SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2", size = 3177897 }, + { url = "https://files.pythonhosted.org/packages/b3/cd/e16f3cbefd82b5c40b33732da634ec67a5f33b587744c7ab41699789d492/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf", size = 3111289 }, + { url = "https://files.pythonhosted.org/packages/15/85/5b8a3b0bc29c9928aa62b5c91fcc8335f57c1de0a6343873b5f372e3672b/SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c", size = 3139491 }, + { url = "https://files.pythonhosted.org/packages/a1/95/81babb6089938680dfe2cd3f88cd3fd39cccd1543b7cb603b21ad881bff1/SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436", size = 2060439 }, + { url = "https://files.pythonhosted.org/packages/c1/ce/5f7428df55660d6879d0522adc73a3364970b5ef33ec17fa125c5dbcac1d/SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88", size = 2084574 }, + { url = "https://files.pythonhosted.org/packages/b8/49/21633706dd6feb14cd3f7935fc00b60870ea057686035e1a99ae6d9d9d53/SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", size = 1883787 }, ] [package.optional-dependencies]